@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
@@ -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 } from "../utils.ts";
8
9
  import {
9
10
  AlterTableAddColumn,
@@ -47,6 +48,12 @@ import {
47
48
  RevokeGrantOptionTablePrivileges,
48
49
  RevokeTablePrivileges,
49
50
  } from "./changes/table.privilege.ts";
51
+ import {
52
+ CreateSecurityLabelOnColumn,
53
+ CreateSecurityLabelOnTable,
54
+ DropSecurityLabelOnColumn,
55
+ DropSecurityLabelOnTable,
56
+ } from "./changes/table.security-label.ts";
50
57
  import type { TableChange } from "./changes/table.types.ts";
51
58
  import { Table } from "./table.model.ts";
52
59
 
@@ -245,15 +252,13 @@ export function diffTables(
245
252
 
246
253
  // REPLICA IDENTITY: If non-default, emit ALTER TABLE ... REPLICA IDENTITY
247
254
  if (branchTable.replica_identity !== "d") {
248
- // Skip 'i' (USING INDEX) — handled by index changes
249
- if (branchTable.replica_identity !== "i") {
250
- changes.push(
251
- new AlterTableSetReplicaIdentity({
252
- table: branchTable,
253
- mode: branchTable.replica_identity,
254
- }),
255
- );
256
- }
255
+ changes.push(
256
+ new AlterTableSetReplicaIdentity({
257
+ table: branchTable,
258
+ mode: branchTable.replica_identity,
259
+ indexName: branchTable.replica_identity_index,
260
+ }),
261
+ );
257
262
  }
258
263
 
259
264
  changes.push(
@@ -281,6 +286,29 @@ export function diffTables(
281
286
  }
282
287
  }
283
288
 
289
+ // Table security labels on creation
290
+ for (const label of branchTable.security_labels) {
291
+ changes.push(
292
+ new CreateSecurityLabelOnTable({
293
+ table: branchTable,
294
+ securityLabel: label,
295
+ }),
296
+ );
297
+ }
298
+
299
+ // Column security labels on creation
300
+ for (const col of branchTable.columns) {
301
+ for (const label of col.security_labels ?? []) {
302
+ changes.push(
303
+ new CreateSecurityLabelOnColumn({
304
+ table: branchTable,
305
+ column: col,
306
+ securityLabel: label,
307
+ }),
308
+ );
309
+ }
310
+ }
311
+
284
312
  // PRIVILEGES: For created objects, compare against default privileges state
285
313
  // The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
286
314
  // so objects are created with the default privileges state in effect.
@@ -404,16 +432,23 @@ export function diffTables(
404
432
  }
405
433
 
406
434
  // REPLICA IDENTITY
407
- if (mainTable.replica_identity !== branchTable.replica_identity) {
408
- // Skip when target is 'i' (USING INDEX) handled by index changes
409
- if (branchTable.replica_identity !== "i") {
410
- changes.push(
411
- new AlterTableSetReplicaIdentity({
412
- table: mainTable,
413
- mode: branchTable.replica_identity,
414
- }),
415
- );
416
- }
435
+ // Re-emit when the mode changes, or when staying in 'i' mode but pointing
436
+ // at a different index. The index named on the branch must already exist
437
+ // before this ALTER runs; AlterTableSetReplicaIdentity declares that
438
+ // dependency in its `requires`.
439
+ const replicaIdentityChanged =
440
+ mainTable.replica_identity !== branchTable.replica_identity ||
441
+ (branchTable.replica_identity === "i" &&
442
+ mainTable.replica_identity_index !==
443
+ branchTable.replica_identity_index);
444
+ if (replicaIdentityChanged) {
445
+ changes.push(
446
+ new AlterTableSetReplicaIdentity({
447
+ table: mainTable,
448
+ mode: branchTable.replica_identity,
449
+ indexName: branchTable.replica_identity_index,
450
+ }),
451
+ );
417
452
  }
418
453
 
419
454
  // OWNER
@@ -435,6 +470,26 @@ export function diffTables(
435
470
  }
436
471
  }
437
472
 
473
+ // TABLE SECURITY LABELS
474
+ changes.push(
475
+ ...diffSecurityLabels<
476
+ CreateSecurityLabelOnTable | DropSecurityLabelOnTable
477
+ >(
478
+ mainTable.security_labels,
479
+ branchTable.security_labels,
480
+ (securityLabel) =>
481
+ new CreateSecurityLabelOnTable({
482
+ table: branchTable,
483
+ securityLabel,
484
+ }),
485
+ (securityLabel) =>
486
+ new DropSecurityLabelOnTable({
487
+ table: mainTable,
488
+ securityLabel,
489
+ }),
490
+ ),
491
+ );
492
+
438
493
  // PARTITION ATTACH/DETACH
439
494
  const mainIsPartition = Boolean(
440
495
  mainTable.parent_schema && mainTable.parent_name,
@@ -881,6 +936,43 @@ export function diffTables(
881
936
  );
882
937
  }
883
938
  }
939
+
940
+ // SECURITY LABELS on column
941
+ changes.push(
942
+ ...diffSecurityLabels<
943
+ CreateSecurityLabelOnColumn | DropSecurityLabelOnColumn
944
+ >(
945
+ mainCol.security_labels ?? [],
946
+ branchCol.security_labels ?? [],
947
+ (securityLabel) =>
948
+ new CreateSecurityLabelOnColumn({
949
+ table: branchTable,
950
+ column: branchCol,
951
+ securityLabel,
952
+ }),
953
+ (securityLabel) =>
954
+ new DropSecurityLabelOnColumn({
955
+ table: mainTable,
956
+ column: mainCol,
957
+ securityLabel,
958
+ }),
959
+ ),
960
+ );
961
+ }
962
+
963
+ // Added columns with security labels (for created columns on existing tables)
964
+ for (const [name, col] of branchCols) {
965
+ if (!mainCols.has(name)) {
966
+ for (const label of col.security_labels ?? []) {
967
+ changes.push(
968
+ new CreateSecurityLabelOnColumn({
969
+ table: branchTable,
970
+ column: col,
971
+ securityLabel: label,
972
+ }),
973
+ );
974
+ }
975
+ }
884
976
  }
885
977
 
886
978
  // PRIVILEGES (unified object and column privileges)
@@ -0,0 +1,209 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { Pool } from "pg";
3
+ import { extractTables, Table } from "./table.model.ts";
4
+
5
+ // Minimal fields required by tablePropsSchema; individual tests override the
6
+ // constraints array (and any other relevant fields).
7
+ const baseTableRow = {
8
+ schema: "public",
9
+ name: '"users"',
10
+ persistence: "p" as const,
11
+ row_security: false,
12
+ force_row_security: false,
13
+ has_indexes: false,
14
+ has_rules: false,
15
+ has_triggers: false,
16
+ has_subclasses: false,
17
+ is_populated: true,
18
+ replica_identity: "d" as const,
19
+ is_partition: false,
20
+ options: null,
21
+ partition_bound: null,
22
+ partition_by: null,
23
+ owner: "postgres",
24
+ comment: null,
25
+ parent_schema: null,
26
+ parent_name: null,
27
+ columns: [],
28
+ privileges: [],
29
+ };
30
+
31
+ const baseConstraint = {
32
+ name: '"users_pkey"',
33
+ constraint_type: "p" as const,
34
+ deferrable: false,
35
+ initially_deferred: false,
36
+ validated: true,
37
+ is_local: true,
38
+ no_inherit: false,
39
+ is_temporal: false,
40
+ is_partition_clone: false,
41
+ parent_constraint_schema: null,
42
+ parent_constraint_name: null,
43
+ parent_table_schema: null,
44
+ parent_table_name: null,
45
+ key_columns: ['"id"'],
46
+ foreign_key_columns: null,
47
+ foreign_key_table: null,
48
+ foreign_key_schema: null,
49
+ foreign_key_table_is_partition: null,
50
+ foreign_key_parent_schema: null,
51
+ foreign_key_parent_table: null,
52
+ foreign_key_effective_schema: null,
53
+ foreign_key_effective_table: null,
54
+ on_update: null,
55
+ on_delete: null,
56
+ match_type: null,
57
+ check_expression: null,
58
+ owner: "postgres",
59
+ comment: null,
60
+ };
61
+
62
+ const mockPool = (rows: unknown[]): Pool =>
63
+ ({ query: async () => ({ rows }) }) as unknown as Pool;
64
+
65
+ const mockPoolSequence = (...attempts: unknown[][]): Pool => {
66
+ let i = 0;
67
+ return {
68
+ query: async () => ({
69
+ rows: attempts[Math.min(i++, attempts.length - 1)],
70
+ }),
71
+ } as unknown as Pool;
72
+ };
73
+
74
+ const NO_BACKOFF = { backoffMs: 0 } as const;
75
+
76
+ describe("extractTables", () => {
77
+ test("skips constraints where pg_get_constraintdef returned NULL after exhausting retries", async () => {
78
+ const tables = await extractTables(
79
+ mockPool([
80
+ {
81
+ ...baseTableRow,
82
+ constraints: [
83
+ {
84
+ ...baseConstraint,
85
+ name: '"users_pkey"',
86
+ definition: "PRIMARY KEY (id)",
87
+ },
88
+ {
89
+ ...baseConstraint,
90
+ name: '"users_orphan_chk"',
91
+ constraint_type: "c",
92
+ key_columns: [],
93
+ definition: null,
94
+ },
95
+ ],
96
+ },
97
+ ]),
98
+ NO_BACKOFF,
99
+ );
100
+
101
+ expect(tables).toHaveLength(1);
102
+ expect(tables[0]).toBeInstanceOf(Table);
103
+ expect(tables[0]?.constraints).toHaveLength(1);
104
+ expect(tables[0]?.constraints[0]?.name).toBe('"users_pkey"');
105
+ expect(tables[0]?.constraints[0]?.definition).toBe("PRIMARY KEY (id)");
106
+ });
107
+
108
+ test("does not throw ZodError when every constraint has a null definition", async () => {
109
+ const tables = await extractTables(
110
+ mockPool([
111
+ {
112
+ ...baseTableRow,
113
+ constraints: [
114
+ {
115
+ ...baseConstraint,
116
+ name: '"orphan_a"',
117
+ constraint_type: "c",
118
+ key_columns: [],
119
+ definition: null,
120
+ },
121
+ {
122
+ ...baseConstraint,
123
+ name: '"orphan_b"',
124
+ constraint_type: "c",
125
+ key_columns: [],
126
+ definition: null,
127
+ },
128
+ ],
129
+ },
130
+ ]),
131
+ NO_BACKOFF,
132
+ );
133
+
134
+ expect(tables).toHaveLength(1);
135
+ expect(tables[0]?.constraints).toEqual([]);
136
+ });
137
+
138
+ test("returns all constraints when every definition is valid", async () => {
139
+ const tables = await extractTables(
140
+ mockPool([
141
+ {
142
+ ...baseTableRow,
143
+ constraints: [
144
+ {
145
+ ...baseConstraint,
146
+ name: '"users_pkey"',
147
+ definition: "PRIMARY KEY (id)",
148
+ },
149
+ {
150
+ ...baseConstraint,
151
+ name: '"users_email_key"',
152
+ constraint_type: "u",
153
+ key_columns: ['"email"'],
154
+ definition: "UNIQUE (email)",
155
+ },
156
+ ],
157
+ },
158
+ ]),
159
+ NO_BACKOFF,
160
+ );
161
+
162
+ expect(tables[0]?.constraints.map((c) => c.name)).toEqual([
163
+ '"users_pkey"',
164
+ '"users_email_key"',
165
+ ]);
166
+ });
167
+
168
+ test("recovers when pg_get_constraintdef is NULL on first attempt but resolved on retry", async () => {
169
+ const tables = await extractTables(
170
+ mockPoolSequence(
171
+ // attempt 1: one constraint has NULL definition
172
+ [
173
+ {
174
+ ...baseTableRow,
175
+ constraints: [
176
+ {
177
+ ...baseConstraint,
178
+ name: '"users_racy_chk"',
179
+ constraint_type: "c",
180
+ key_columns: [],
181
+ definition: null,
182
+ },
183
+ ],
184
+ },
185
+ ],
186
+ // attempt 2: constraint resolves on retry
187
+ [
188
+ {
189
+ ...baseTableRow,
190
+ constraints: [
191
+ {
192
+ ...baseConstraint,
193
+ name: '"users_racy_chk"',
194
+ constraint_type: "c",
195
+ key_columns: [],
196
+ definition: "CHECK (id > 0)",
197
+ },
198
+ ],
199
+ },
200
+ ],
201
+ ),
202
+ { retries: 2, backoffMs: 0 },
203
+ );
204
+ expect(tables).toHaveLength(1);
205
+ expect(tables[0]?.constraints).toHaveLength(1);
206
+ expect(tables[0]?.constraints[0]?.name).toBe('"users_racy_chk"');
207
+ expect(tables[0]?.constraints[0]?.definition).toBe("CHECK (id > 0)");
208
+ });
209
+ });
@@ -12,6 +12,15 @@ import {
12
12
  type PrivilegeProps,
13
13
  privilegePropsSchema,
14
14
  } from "../base.privilege-diff.ts";
15
+ import {
16
+ type ExtractRetryOptions,
17
+ extractWithDefinitionRetry,
18
+ } from "../extract-with-retry.ts";
19
+ import {
20
+ normalizeSecurityLabels,
21
+ type SecurityLabelProps,
22
+ securityLabelPropsSchema,
23
+ } from "../security-label.types.ts";
15
24
 
16
25
  const RelationPersistenceSchema = z.enum([
17
26
  "p", // permanent
@@ -82,6 +91,15 @@ const tableConstraintPropsSchema = z.object({
82
91
 
83
92
  export type TableConstraintProps = z.infer<typeof tableConstraintPropsSchema>;
84
93
 
94
+ // pg_get_constraintdef(oid, pretty) can return NULL under the same conditions
95
+ // as pg_get_indexdef: races with concurrent DDL, transient catalog
96
+ // inconsistencies, recovery edges. An unreadable constraint cannot be diffed,
97
+ // so we accept NULL here and filter the constraint out at extraction time
98
+ // rather than crashing the whole catalog parse with a ZodError.
99
+ const tableConstraintRowSchema = tableConstraintPropsSchema.extend({
100
+ definition: z.string().nullable(),
101
+ });
102
+
85
103
  const tablePropsSchema = z.object({
86
104
  schema: z.string(),
87
105
  name: z.string(),
@@ -94,6 +112,7 @@ const tablePropsSchema = z.object({
94
112
  has_subclasses: z.boolean(),
95
113
  is_populated: z.boolean(),
96
114
  replica_identity: ReplicaIdentitySchema,
115
+ replica_identity_index: z.string().nullable().optional(),
97
116
  is_partition: z.boolean(),
98
117
  options: z.array(z.string()).nullable(),
99
118
  partition_bound: z.string().nullable(),
@@ -105,10 +124,20 @@ const tablePropsSchema = z.object({
105
124
  columns: z.array(columnPropsSchema),
106
125
  constraints: z.array(tableConstraintPropsSchema).optional(),
107
126
  privileges: z.array(privilegePropsSchema),
127
+ security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
128
+ });
129
+
130
+ const tableRowSchema = tablePropsSchema.extend({
131
+ constraints: z.array(tableConstraintRowSchema).optional(),
108
132
  });
109
133
 
110
134
  type TablePrivilegeProps = PrivilegeProps;
135
+ /**
136
+ * Table input props. `security_labels` is optional on direct construction
137
+ * (defaults to `[]`); extraction always produces it via the Zod default.
138
+ */
111
139
  export type TableProps = z.infer<typeof tablePropsSchema>;
140
+ type TableRow = z.infer<typeof tableRowSchema>;
112
141
 
113
142
  export class Table extends BasePgModel implements TableLikeObject {
114
143
  public readonly schema: TableProps["schema"];
@@ -122,6 +151,7 @@ export class Table extends BasePgModel implements TableLikeObject {
122
151
  public readonly has_subclasses: TableProps["has_subclasses"];
123
152
  public readonly is_populated: TableProps["is_populated"];
124
153
  public readonly replica_identity: TableProps["replica_identity"];
154
+ public readonly replica_identity_index: TableProps["replica_identity_index"];
125
155
  public readonly is_partition: TableProps["is_partition"];
126
156
  public readonly options: TableProps["options"];
127
157
  public readonly partition_bound: TableProps["partition_bound"];
@@ -133,6 +163,7 @@ export class Table extends BasePgModel implements TableLikeObject {
133
163
  public readonly columns: TableProps["columns"];
134
164
  public readonly constraints: TableConstraintProps[];
135
165
  public readonly privileges: TablePrivilegeProps[];
166
+ public readonly security_labels: SecurityLabelProps[];
136
167
 
137
168
  constructor(props: TableProps) {
138
169
  super();
@@ -151,6 +182,7 @@ export class Table extends BasePgModel implements TableLikeObject {
151
182
  this.has_subclasses = props.has_subclasses;
152
183
  this.is_populated = props.is_populated;
153
184
  this.replica_identity = props.replica_identity;
185
+ this.replica_identity_index = props.replica_identity_index ?? null;
154
186
  this.is_partition = props.is_partition;
155
187
  this.options = props.options;
156
188
  this.partition_bound = props.partition_bound;
@@ -162,6 +194,7 @@ export class Table extends BasePgModel implements TableLikeObject {
162
194
  this.columns = props.columns;
163
195
  this.constraints = props.constraints ?? [];
164
196
  this.privileges = props.privileges;
197
+ this.security_labels = props.security_labels ?? [];
165
198
  }
166
199
 
167
200
  get stableId(): `table:${string}` {
@@ -182,6 +215,7 @@ export class Table extends BasePgModel implements TableLikeObject {
182
215
  row_security: this.row_security,
183
216
  force_row_security: this.force_row_security,
184
217
  replica_identity: this.replica_identity,
218
+ replica_identity_index: this.replica_identity_index,
185
219
  options: this.options,
186
220
  // Partition membership can be altered via ATTACH/DETACH
187
221
  parent_schema: this.parent_schema,
@@ -192,6 +226,7 @@ export class Table extends BasePgModel implements TableLikeObject {
192
226
  columns: this.columns,
193
227
  constraints: this.constraints,
194
228
  privileges: this.privileges,
229
+ security_labels: this.security_labels,
195
230
  };
196
231
  }
197
232
 
@@ -211,13 +246,23 @@ export class Table extends BasePgModel implements TableLikeObject {
211
246
  options: this.options ? [...this.options].sort() : this.options,
212
247
  constraints: normalizeConstraints(),
213
248
  privileges: normalizePrivileges(this.privileges),
249
+ security_labels: normalizeSecurityLabels(this.security_labels),
214
250
  },
215
251
  };
216
252
  }
217
253
  }
218
254
 
219
- export async function extractTables(pool: Pool): Promise<Table[]> {
220
- const { rows: tableRows } = await pool.query<TableProps>(sql`
255
+ export async function extractTables(
256
+ pool: Pool,
257
+ options?: ExtractRetryOptions,
258
+ ): Promise<Table[]> {
259
+ const tableRows = await extractWithDefinitionRetry({
260
+ label: "table constraints",
261
+ options,
262
+ hasNullDefinition: (row: TableRow) =>
263
+ row.constraints?.some((c) => c.definition === null) ?? false,
264
+ query: async () => {
265
+ const result = await pool.query<TableProps>(sql`
221
266
  with extension_oids as (
222
267
  select objid
223
268
  from pg_depend d
@@ -236,6 +281,14 @@ with extension_oids as (
236
281
  c.relhassubclass as has_subclasses,
237
282
  c.relispopulated as is_populated,
238
283
  c.relreplident as replica_identity,
284
+ (
285
+ select quote_ident(ri_class.relname)
286
+ from pg_index ri
287
+ join pg_class ri_class on ri_class.oid = ri.indexrelid
288
+ where ri.indrelid = c.oid
289
+ and ri.indisreplident is true
290
+ limit 1
291
+ ) as replica_identity_index,
239
292
  c.relispartition as is_partition,
240
293
  c.reloptions as options,
241
294
  pg_get_expr(c.relpartbound, c.oid) as partition_bound,
@@ -266,6 +319,7 @@ select
266
319
  t.has_subclasses,
267
320
  t.is_populated,
268
321
  t.replica_identity,
322
+ t.replica_identity_index,
269
323
  t.is_partition,
270
324
  t.options,
271
325
  t.partition_bound,
@@ -406,7 +460,20 @@ select
406
460
  and a.attcollation <> t2.typcollation
407
461
  ),
408
462
  'default', pg_get_expr(ad.adbin, ad.adrelid),
409
- 'comment', col_description(a.attrelid, a.attnum)
463
+ 'comment', col_description(a.attrelid, a.attnum),
464
+ 'security_labels', coalesce(
465
+ (
466
+ select json_agg(
467
+ json_build_object('provider', sl.provider, 'label', sl.label)
468
+ order by sl.provider
469
+ )
470
+ from pg_catalog.pg_seclabel sl
471
+ where sl.objoid = t.oid
472
+ and sl.classoid = 'pg_class'::regclass
473
+ and sl.objsubid = a.attnum
474
+ ),
475
+ '[]'::json
476
+ )
410
477
  )
411
478
  end
412
479
  order by a.attnum
@@ -446,20 +513,38 @@ select
446
513
  join lateral aclexplode(src.acl) as x(grantor, grantee, privilege_type, is_grantable) on true
447
514
  group by x.grantee, x.privilege_type
448
515
  ) as grp
449
- ), '[]') as privileges
516
+ ), '[]') as privileges,
517
+ coalesce(
518
+ (
519
+ select json_agg(
520
+ json_build_object('provider', sl.provider, 'label', sl.label)
521
+ order by sl.provider
522
+ )
523
+ from pg_catalog.pg_seclabel sl
524
+ where sl.objoid = t.oid
525
+ and sl.classoid = 'pg_class'::regclass
526
+ and sl.objsubid = 0
527
+ ),
528
+ '[]'::json
529
+ ) as security_labels
450
530
  from
451
531
  tables t
452
532
  left join pg_attribute a on a.attrelid = t.oid and a.attnum > 0 and not a.attisdropped
453
533
  left join pg_attrdef ad on a.attrelid = ad.adrelid and a.attnum = ad.adnum
454
534
  left join pg_type ty on ty.oid = a.atttypid
455
535
  group by
456
- t.oid, t.schema, t.name, t.persistence, t.row_security, t.force_row_security, t.has_indexes, t.has_rules, t.has_triggers, t.has_subclasses, t.is_populated, t.replica_identity, t.is_partition, t.options, t.partition_bound, t.partition_by, t.owner, t.parent_schema, t.parent_name
536
+ t.oid, t.schema, t.name, t.persistence, t.row_security, t.force_row_security, t.has_indexes, t.has_rules, t.has_triggers, t.has_subclasses, t.is_populated, t.replica_identity, t.replica_identity_index, t.is_partition, t.options, t.partition_bound, t.partition_by, t.owner, t.parent_schema, t.parent_name
457
537
  order by
458
538
  t.schema, t.name
459
539
  `);
460
- // Validate and parse each row using the Zod schema
461
- const validatedRows = tableRows.map((row: unknown) =>
462
- tablePropsSchema.parse(row),
463
- );
540
+ return result.rows.map((row: unknown) => tableRowSchema.parse(row));
541
+ },
542
+ });
543
+ const validatedRows = tableRows.map((row): TableProps => {
544
+ const filteredConstraints = row.constraints?.filter(
545
+ (c): c is TableConstraintProps => c.definition !== null,
546
+ );
547
+ return { ...row, constraints: filteredConstraints };
548
+ });
464
549
  return validatedRows.map((row: TableProps) => new Table(row));
465
550
  }