@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
@@ -0,0 +1,117 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { Pool } from "pg";
3
+ import { extractProcedures, Procedure } from "./procedure.model.ts";
4
+
5
+ const baseRow = {
6
+ schema: "public",
7
+ kind: "f" as const,
8
+ return_type: "integer",
9
+ return_type_schema: "pg_catalog",
10
+ language: "sql",
11
+ security_definer: false,
12
+ volatility: "v" as const,
13
+ parallel_safety: "u" as const,
14
+ execution_cost: 100,
15
+ result_rows: 0,
16
+ is_strict: false,
17
+ leakproof: false,
18
+ returns_set: false,
19
+ argument_count: 0,
20
+ argument_default_count: 0,
21
+ argument_names: null,
22
+ argument_types: null,
23
+ all_argument_types: null,
24
+ argument_modes: null,
25
+ argument_defaults: null,
26
+ source_code: "select 1",
27
+ binary_path: null,
28
+ sql_body: null,
29
+ config: null,
30
+ owner: "postgres",
31
+ comment: null,
32
+ privileges: [],
33
+ };
34
+
35
+ const mockPool = (rows: unknown[]): Pool =>
36
+ ({ query: async () => ({ rows }) }) as unknown as Pool;
37
+
38
+ const mockPoolSequence = (...attempts: unknown[][]): Pool => {
39
+ let i = 0;
40
+ return {
41
+ query: async () => ({
42
+ rows: attempts[Math.min(i++, attempts.length - 1)],
43
+ }),
44
+ } as unknown as Pool;
45
+ };
46
+
47
+ const NO_BACKOFF = { backoffMs: 0 } as const;
48
+
49
+ describe("extractProcedures", () => {
50
+ test("skips rows where pg_get_functiondef returned NULL after exhausting retries", async () => {
51
+ const procs = await extractProcedures(
52
+ mockPool([
53
+ {
54
+ ...baseRow,
55
+ name: '"good_fn"',
56
+ definition:
57
+ "CREATE OR REPLACE FUNCTION good_fn() RETURNS integer AS $$ select 1 $$ LANGUAGE sql;",
58
+ },
59
+ { ...baseRow, name: '"orphan_fn"', definition: null },
60
+ ]),
61
+ NO_BACKOFF,
62
+ );
63
+
64
+ expect(procs).toHaveLength(1);
65
+ expect(procs[0]).toBeInstanceOf(Procedure);
66
+ expect(procs[0]?.name).toBe('"good_fn"');
67
+ });
68
+
69
+ test("does not throw ZodError when the only row has a null definition", async () => {
70
+ await expect(
71
+ extractProcedures(
72
+ mockPool([{ ...baseRow, name: '"orphan"', definition: null }]),
73
+ NO_BACKOFF,
74
+ ),
75
+ ).resolves.toEqual([]);
76
+ });
77
+
78
+ test("returns all procedures when every row has a valid definition", async () => {
79
+ const procs = await extractProcedures(
80
+ mockPool([
81
+ {
82
+ ...baseRow,
83
+ name: '"a"',
84
+ definition:
85
+ "CREATE OR REPLACE FUNCTION a() RETURNS integer AS $$ select 1 $$ LANGUAGE sql;",
86
+ },
87
+ {
88
+ ...baseRow,
89
+ name: '"b"',
90
+ definition:
91
+ "CREATE OR REPLACE FUNCTION b() RETURNS integer AS $$ select 2 $$ LANGUAGE sql;",
92
+ },
93
+ ]),
94
+ NO_BACKOFF,
95
+ );
96
+ expect(procs.map((p) => p.name)).toEqual(['"a"', '"b"']);
97
+ });
98
+
99
+ test("recovers when pg_get_functiondef is NULL on first attempt but resolved on retry", async () => {
100
+ const procs = await extractProcedures(
101
+ mockPoolSequence(
102
+ [{ ...baseRow, name: '"racy_fn"', definition: null }],
103
+ [
104
+ {
105
+ ...baseRow,
106
+ name: '"racy_fn"',
107
+ definition:
108
+ "CREATE OR REPLACE FUNCTION racy_fn() RETURNS integer AS $$ select 1 $$ LANGUAGE sql;",
109
+ },
110
+ ],
111
+ ),
112
+ { retries: 2, backoffMs: 0 },
113
+ );
114
+ expect(procs).toHaveLength(1);
115
+ expect(procs[0]?.name).toBe('"racy_fn"');
116
+ });
117
+ });
@@ -6,6 +6,14 @@ import {
6
6
  type PrivilegeProps,
7
7
  privilegePropsSchema,
8
8
  } from "../base.privilege-diff.ts";
9
+ import {
10
+ type ExtractRetryOptions,
11
+ extractWithDefinitionRetry,
12
+ } from "../extract-with-retry.ts";
13
+ import {
14
+ type SecurityLabelProps,
15
+ securityLabelPropsSchema,
16
+ } from "../security-label.types.ts";
9
17
 
10
18
  const FunctionKindSchema = z.enum([
11
19
  "f", // function
@@ -64,6 +72,16 @@ const procedurePropsSchema = z.object({
64
72
  owner: z.string(),
65
73
  comment: z.string().nullable(),
66
74
  privileges: z.array(privilegePropsSchema),
75
+ security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
76
+ });
77
+
78
+ // pg_get_functiondef(oid) can return NULL when the function (its pg_proc
79
+ // row) is dropped between catalog scan and resolution, or under transient
80
+ // catalog state. An unreadable function cannot be diffed, so we accept NULL
81
+ // here and filter the row out at extraction time rather than crashing the
82
+ // whole catalog parse with a ZodError.
83
+ const procedureRowSchema = procedurePropsSchema.extend({
84
+ definition: z.string().nullable(),
67
85
  });
68
86
 
69
87
  type ProcedurePrivilegeProps = PrivilegeProps;
@@ -99,6 +117,7 @@ export class Procedure extends BasePgModel {
99
117
  public readonly owner: ProcedureProps["owner"];
100
118
  public readonly comment: ProcedureProps["comment"];
101
119
  public readonly privileges: ProcedurePrivilegeProps[];
120
+ public readonly security_labels: SecurityLabelProps[];
102
121
 
103
122
  constructor(props: ProcedureProps) {
104
123
  super();
@@ -135,6 +154,7 @@ export class Procedure extends BasePgModel {
135
154
  this.owner = props.owner;
136
155
  this.comment = props.comment;
137
156
  this.privileges = props.privileges;
157
+ this.security_labels = props.security_labels ?? [];
138
158
  }
139
159
 
140
160
  get stableId(): `procedure:${string}` {
@@ -179,12 +199,21 @@ export class Procedure extends BasePgModel {
179
199
  owner: this.owner,
180
200
  comment: this.comment,
181
201
  privileges: this.privileges,
202
+ security_labels: this.security_labels,
182
203
  };
183
204
  }
184
205
  }
185
206
 
186
- export async function extractProcedures(pool: Pool): Promise<Procedure[]> {
187
- const { rows: procedureRows } = await pool.query<ProcedureProps>(sql`
207
+ export async function extractProcedures(
208
+ pool: Pool,
209
+ options?: ExtractRetryOptions,
210
+ ): Promise<Procedure[]> {
211
+ const procedureRows = await extractWithDefinitionRetry({
212
+ label: "procedures",
213
+ options,
214
+ hasNullDefinition: (row) => row.definition === null,
215
+ query: async () => {
216
+ const result = await pool.query<ProcedureProps>(sql`
188
217
  with extension_oids as (
189
218
  select
190
219
  objid
@@ -244,7 +273,20 @@ select
244
273
  )
245
274
  from lateral aclexplode(COALESCE(p.proacl, acldefault('f', p.proowner))) as x(grantor, grantee, privilege_type, is_grantable)
246
275
  ), '[]'
247
- ) as privileges
276
+ ) as privileges,
277
+ coalesce(
278
+ (
279
+ select json_agg(
280
+ json_build_object('provider', sl.provider, 'label', sl.label)
281
+ order by sl.provider
282
+ )
283
+ from pg_catalog.pg_seclabel sl
284
+ where sl.objoid = p.oid
285
+ and sl.classoid = 'pg_proc'::regclass
286
+ and sl.objsubid = 0
287
+ ),
288
+ '[]'::json
289
+ ) as security_labels
248
290
  from
249
291
  pg_catalog.pg_proc p
250
292
  inner join pg_catalog.pg_language l on l.oid = p.prolang
@@ -256,9 +298,11 @@ from
256
298
  order by
257
299
  1, 2
258
300
  `);
259
- // Validate and parse each row using the Zod schema
260
- const validatedRows = procedureRows.map((row: unknown) =>
261
- procedurePropsSchema.parse(row),
301
+ return result.rows.map((row: unknown) => procedureRowSchema.parse(row));
302
+ },
303
+ });
304
+ const validatedRows = procedureRows.filter(
305
+ (row): row is ProcedureProps => row.definition !== null,
262
306
  );
263
- return validatedRows.map((row: ProcedureProps) => new Procedure(row));
307
+ return validatedRows.map((row) => new Procedure(row));
264
308
  }
@@ -3,7 +3,7 @@ import type { Publication } from "../publication.model.ts";
3
3
 
4
4
  abstract class BasePublicationChange extends BaseChange {
5
5
  abstract readonly publication: Publication;
6
- abstract readonly scope: "object" | "comment";
6
+ abstract readonly scope: "object" | "comment" | "security_label";
7
7
  readonly objectType = "publication" as const;
8
8
  }
9
9
 
@@ -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 { Publication } from "../publication.model.ts";
5
+ import {
6
+ CreatePublicationChange,
7
+ DropPublicationChange,
8
+ } from "./publication.base.ts";
9
+
10
+ export type SecurityLabelPublication =
11
+ | CreateSecurityLabelOnPublication
12
+ | DropSecurityLabelOnPublication;
13
+
14
+ export class CreateSecurityLabelOnPublication extends CreatePublicationChange {
15
+ public readonly publication: Publication;
16
+ public readonly securityLabel: SecurityLabelProps;
17
+ public readonly scope = "security_label" as const;
18
+
19
+ constructor(props: {
20
+ publication: Publication;
21
+ securityLabel: SecurityLabelProps;
22
+ }) {
23
+ super();
24
+ this.publication = props.publication;
25
+ this.securityLabel = props.securityLabel;
26
+ }
27
+
28
+ get creates() {
29
+ return [
30
+ stableId.securityLabel(
31
+ this.publication.stableId,
32
+ this.securityLabel.provider,
33
+ ),
34
+ ];
35
+ }
36
+
37
+ get requires() {
38
+ return [this.publication.stableId];
39
+ }
40
+
41
+ serialize(): string {
42
+ return [
43
+ "SECURITY LABEL FOR",
44
+ this.securityLabel.provider,
45
+ "ON PUBLICATION",
46
+ this.publication.name,
47
+ "IS",
48
+ quoteLiteral(this.securityLabel.label),
49
+ ].join(" ");
50
+ }
51
+ }
52
+
53
+ export class DropSecurityLabelOnPublication extends DropPublicationChange {
54
+ public readonly publication: Publication;
55
+ public readonly securityLabel: SecurityLabelProps;
56
+ public readonly scope = "security_label" as const;
57
+
58
+ constructor(props: {
59
+ publication: Publication;
60
+ securityLabel: SecurityLabelProps;
61
+ }) {
62
+ super();
63
+ this.publication = props.publication;
64
+ this.securityLabel = props.securityLabel;
65
+ }
66
+
67
+ get drops() {
68
+ return [
69
+ stableId.securityLabel(
70
+ this.publication.stableId,
71
+ this.securityLabel.provider,
72
+ ),
73
+ ];
74
+ }
75
+
76
+ get requires() {
77
+ return [
78
+ stableId.securityLabel(
79
+ this.publication.stableId,
80
+ this.securityLabel.provider,
81
+ ),
82
+ this.publication.stableId,
83
+ ];
84
+ }
85
+
86
+ serialize(): string {
87
+ return [
88
+ "SECURITY LABEL FOR",
89
+ this.securityLabel.provider,
90
+ "ON PUBLICATION",
91
+ this.publication.name,
92
+ "IS NULL",
93
+ ].join(" ");
94
+ }
95
+ }
@@ -10,6 +10,7 @@ import type {
10
10
  import type { CommentPublication } from "./publication.comment.ts";
11
11
  import type { CreatePublication } from "./publication.create.ts";
12
12
  import type { DropPublication } from "./publication.drop.ts";
13
+ import type { SecurityLabelPublication } from "./publication.security-label.ts";
13
14
 
14
15
  /** Union of all publication-related change variants (`objectType: "publication"`). @category Change Types */
15
16
  export type PublicationChange =
@@ -22,4 +23,5 @@ export type PublicationChange =
22
23
  | AlterPublicationSetOwner
23
24
  | CommentPublication
24
25
  | CreatePublication
25
- | DropPublication;
26
+ | DropPublication
27
+ | SecurityLabelPublication;
@@ -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
@@ -59,7 +59,25 @@ export function diffRlsPolicies(
59
59
  {},
60
60
  );
61
61
 
62
- if (nonAlterablePropsChanged) {
62
+ // The set of relations and procedures that the policy's USING / WITH
63
+ // CHECK expressions reference is recorded by PostgreSQL in pg_depend
64
+ // (recordDependencyOnExpr at policy creation). When that set changes
65
+ // it is unsafe to ALTER POLICY in place: the old reference target may
66
+ // be dropped in the same plan, and the new reference target may only
67
+ // exist after the create phase. Drop+create lets the sort phase order
68
+ // the policy's drop before the referenced object's drop and the
69
+ // policy's recreate after the referenced object's create.
70
+ const referencedDependenciesChanged = hasNonAlterableChanges(
71
+ mainRlsPolicy,
72
+ branchRlsPolicy,
73
+ ["referenced_procedures", "referenced_relations"] as const,
74
+ {
75
+ referenced_procedures: deepEqual,
76
+ referenced_relations: deepEqual,
77
+ },
78
+ );
79
+
80
+ if (nonAlterablePropsChanged || referencedDependenciesChanged) {
63
81
  // Replace the entire RLS policy (drop + create)
64
82
  changes.push(
65
83
  new DropRlsPolicy({ policy: mainRlsPolicy }),
@@ -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]));