@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,202 @@
1
+ import { CreateIndex } from "./objects/index/changes/index.create.js";
2
+ import { DropIndex } from "./objects/index/changes/index.drop.js";
3
+ import { AlterTableAddConstraint, AlterTableDropColumn, AlterTableDropConstraint, AlterTableSetReplicaIdentity, AlterTableValidateConstraint, } from "./objects/table/changes/table.alter.js";
4
+ import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.js";
5
+ import { stableId } from "./objects/utils.js";
6
+ function constraintStableId(table, constraintName) {
7
+ return stableId.constraint(table.schema, table.name, constraintName);
8
+ }
9
+ function isSupersededByTableReplacement(change, replacedTableIds) {
10
+ if (!(change instanceof AlterTableDropColumn) &&
11
+ !(change instanceof AlterTableDropConstraint)) {
12
+ return false;
13
+ }
14
+ return replacedTableIds.has(change.table.stableId);
15
+ }
16
+ /**
17
+ * Drop earlier duplicates of `AlterTableAddConstraint` /
18
+ * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` targeting
19
+ * replaced tables, keeping only the last occurrence of each
20
+ * `(changeType, table.stableId, constraint.name)`.
21
+ *
22
+ * When `expandReplaceDependencies()` promotes a table to a full
23
+ * `DropTable + CreateTable` pair, it also emits one
24
+ * `AlterTableAddConstraint` (plus optional `VALIDATE CONSTRAINT` /
25
+ * `COMMENT ON CONSTRAINT`) per branch constraint. If `diffTables()` already
26
+ * emitted the same change for a shape flip or a new constraint on that
27
+ * table, the plan ends up with two identical `ALTER TABLE ... ADD
28
+ * CONSTRAINT ...` statements and PostgreSQL fails at apply time with
29
+ * `constraint "..." for relation "..." already exists`. Because
30
+ * `expandReplaceDependencies()` appends its additions after the original
31
+ * `diffTables()` output, the last occurrence is the expansion's emission —
32
+ * keeping it preserves correctness while removing the duplicate.
33
+ */
34
+ function dropReplacedTableDuplicateConstraintChanges(changes, replacedTableIds) {
35
+ if (replacedTableIds.size === 0)
36
+ return changes;
37
+ const keyFor = (change) => {
38
+ if (!(change instanceof AlterTableAddConstraint) &&
39
+ !(change instanceof AlterTableValidateConstraint) &&
40
+ !(change instanceof CreateCommentOnConstraint)) {
41
+ return null;
42
+ }
43
+ if (!replacedTableIds.has(change.table.stableId))
44
+ return null;
45
+ const tag = change instanceof AlterTableAddConstraint
46
+ ? "add"
47
+ : change instanceof AlterTableValidateConstraint
48
+ ? "validate"
49
+ : "comment";
50
+ return `${tag}:${constraintStableId(change.table, change.constraint.name)}`;
51
+ };
52
+ const seen = new Set();
53
+ const reversedKept = [];
54
+ let mutated = false;
55
+ // Walk backwards: the first encounter of each key corresponds to its LAST
56
+ // occurrence in the original order. `expandReplaceDependencies()` appends
57
+ // additions after the original changes, so "last wins" keeps the
58
+ // expansion's emission and drops the earlier diffTables duplicate.
59
+ for (let i = changes.length - 1; i >= 0; i--) {
60
+ const change = changes[i];
61
+ const key = keyFor(change);
62
+ if (key !== null) {
63
+ if (seen.has(key)) {
64
+ mutated = true;
65
+ continue;
66
+ }
67
+ seen.add(key);
68
+ }
69
+ reversedKept.push(change);
70
+ }
71
+ return mutated ? reversedKept.reverse() : changes;
72
+ }
73
+ /**
74
+ * Re-emit `ALTER TABLE ... REPLICA IDENTITY USING INDEX <idx>` after any
75
+ * `DropIndex(idx) + CreateIndex(idx)` pair where `idx` is the replica-identity
76
+ * index of a branch table.
77
+ *
78
+ * Background: PostgreSQL silently flips a table's `relreplident` to `'d'`
79
+ * (DEFAULT) when the index it points to is dropped. `CREATE INDEX` cannot
80
+ * restore the marker — only `ALTER TABLE ... REPLICA IDENTITY USING INDEX`
81
+ * can. When both main and branch carry `replica_identity = 'i'` pointing at
82
+ * the same index name, `diffTables()` emits no replica-identity change of its
83
+ * own, so the marker would be lost on apply.
84
+ *
85
+ * This is a whole-plan interaction: `diffTables()` cannot detect it without
86
+ * also looking at index changes. Per the "whole-plan interactions belong in
87
+ * post-diff normalization" rule in the package CLAUDE.md, the restoration
88
+ * lives here.
89
+ *
90
+ * Insertion is idempotent: if `diffTables()` already emitted the same
91
+ * `AlterTableSetReplicaIdentity` for this table (e.g. when the user is also
92
+ * switching the replica-identity index name in the same migration), no
93
+ * duplicate is added.
94
+ */
95
+ function restoreReplicaIdentityAfterIndexReplace(changes, branchTables) {
96
+ // Build the index-stable-id → owning-table map from branch state. Only
97
+ // tables in 'i' mode contribute, and only those whose configured index name
98
+ // is non-null (the extractor returns null for any other mode).
99
+ const replicaIdentityIndexToTable = new Map();
100
+ for (const table of Object.values(branchTables)) {
101
+ if (table.replica_identity !== "i" || !table.replica_identity_index) {
102
+ continue;
103
+ }
104
+ const indexId = stableId.index(table.schema, table.name, table.replica_identity_index);
105
+ replicaIdentityIndexToTable.set(indexId, table);
106
+ }
107
+ if (replicaIdentityIndexToTable.size === 0)
108
+ return changes;
109
+ // Find the indexes that are both dropped AND created in this plan. A pure
110
+ // drop or a pure create is handled by `diffTables()` directly (the table's
111
+ // replica_identity / replica_identity_index fields will have changed). The
112
+ // hole is specifically the drop+create pair that recreates the same name.
113
+ const droppedIndexIds = new Set();
114
+ const createdIndexIds = new Set();
115
+ for (const change of changes) {
116
+ if (change instanceof DropIndex) {
117
+ droppedIndexIds.add(change.index.stableId);
118
+ }
119
+ else if (change instanceof CreateIndex) {
120
+ createdIndexIds.add(change.index.stableId);
121
+ }
122
+ }
123
+ const replacedIndexIds = new Set();
124
+ for (const id of droppedIndexIds) {
125
+ if (createdIndexIds.has(id) && replicaIdentityIndexToTable.has(id)) {
126
+ replacedIndexIds.add(id);
127
+ }
128
+ }
129
+ if (replacedIndexIds.size === 0)
130
+ return changes;
131
+ // Skip tables for which `diffTables()` already emitted a replica-identity
132
+ // setter — re-emitting would produce a redundant ALTER TABLE (harmless on
133
+ // apply, but noisy in plan output).
134
+ const tablesWithExistingReplicaIdentitySetter = new Set();
135
+ for (const change of changes) {
136
+ if (change instanceof AlterTableSetReplicaIdentity) {
137
+ tablesWithExistingReplicaIdentitySetter.add(change.table.stableId);
138
+ }
139
+ }
140
+ // Insert one `AlterTableSetReplicaIdentity` per replaced index, immediately
141
+ // after the matching `CreateIndex`. The change's `requires` already names
142
+ // both the table and the recreated index, so the topo sort orders it
143
+ // correctly relative to the surrounding DDL.
144
+ const result = [];
145
+ for (const change of changes) {
146
+ result.push(change);
147
+ if (!(change instanceof CreateIndex) ||
148
+ !replacedIndexIds.has(change.index.stableId)) {
149
+ continue;
150
+ }
151
+ const table = replicaIdentityIndexToTable.get(change.index.stableId);
152
+ if (!table)
153
+ continue;
154
+ if (tablesWithExistingReplicaIdentitySetter.has(table.stableId))
155
+ continue;
156
+ result.push(new AlterTableSetReplicaIdentity({
157
+ table,
158
+ mode: "i",
159
+ indexName: table.replica_identity_index,
160
+ }));
161
+ // Mark as emitted so a second replaced index on the same table — if that
162
+ // ever arises — doesn't double-emit.
163
+ tablesWithExistingReplicaIdentitySetter.add(table.stableId);
164
+ }
165
+ return result;
166
+ }
167
+ /**
168
+ * Apply structural rewrites to the change list that are only obvious once
169
+ * every object diff has been collected. This pass does NOT prevent dependency
170
+ * cycles — that responsibility now lives in the sort phase, where
171
+ * `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
172
+ * that edge filtering can't break (FK SCC of dropped tables,
173
+ * AlterPublicationDropTables ↔ AlterTableDropColumn, …).
174
+ *
175
+ * Concretely, this pass:
176
+ *
177
+ * - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
178
+ * changes that are made redundant by an expansion-emitted
179
+ * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
180
+ * would try to drop a column that no longer exists in the freshly
181
+ * recreated table.
182
+ * - Dedupes duplicate `AlterTableAddConstraint` /
183
+ * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
184
+ * produced when `diffTables()` and `expandReplaceDependencies()` both
185
+ * emit the same constraint operation for a replaced table. Last write
186
+ * wins so the expansion's emission survives.
187
+ * - Re-emits `ALTER TABLE ... REPLICA IDENTITY USING INDEX <idx>` after any
188
+ * `DropIndex(idx) + CreateIndex(idx)` pair where `idx` is the replica
189
+ * identity index of a branch table — Postgres silently clears the marker
190
+ * when the underlying index is dropped, and `CREATE INDEX` cannot restore
191
+ * it.
192
+ *
193
+ * Object-local PostgreSQL semantics (for example owned-sequence cascades)
194
+ * stay in the corresponding `diff*` function instead of this pass.
195
+ */
196
+ export function normalizePostDiffChanges({ changes, replacedTableIds = new Set(), branchTables = {}, }) {
197
+ const restoredChanges = restoreReplicaIdentityAfterIndexReplace(changes, branchTables);
198
+ const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(restoredChanges, replacedTableIds);
199
+ if (replacedTableIds.size === 0)
200
+ return dedupedChanges;
201
+ return dedupedChanges.filter((change) => !isSupersededByTableReplacement(change, replacedTableIds));
202
+ }
@@ -245,7 +245,7 @@ function tryBreakPublicationColumnCycle(cycleNodeIndexes, phaseChanges) {
245
245
  return null;
246
246
  // Verify the table is NOT itself being dropped. If `DropTable(T)` is in
247
247
  // the same phase, the existing structural rewrites in
248
- // `post-diff-cycle-breaking.ts` (replace-expansion superseded filter)
248
+ // `post-diff-normalization.ts` (replace-expansion superseded filter)
249
249
  // already prune the redundant `AlterTableDropColumn`, so we should not
250
250
  // see this combination here. Be defensive and bail anyway — flipping
251
251
  // `omitTableRequirement` when T is being dropped would let the column
@@ -19,5 +19,15 @@ export declare function isMetadataStableId(stableId: string): boolean;
19
19
  * - ALTER operations with scope="privilege" → create_alter_object phase (metadata changes)
20
20
  * - ALTER operations that drop actual objects → drop phase (destructive ALTER)
21
21
  * - ALTER operations that don't drop objects → create_alter_object phase (non-destructive ALTER)
22
+ *
23
+ * Dependency-breaking ALTERs that remove a `pg_depend` edge to another
24
+ * object that may be dropped in the same plan (for example
25
+ * `ALTER COLUMN ... DROP DEFAULT` releasing a sequence reference, or
26
+ * `ALTER COLUMN ... TYPE <built-in>` releasing a user-defined type
27
+ * reference) are routed to the drop phase. The drop phase sorts in reverse
28
+ * dependency order using the main catalog, so the catalog edges already
29
+ * in `pg_depend` order the ALTER before any dependent `DROP TYPE` /
30
+ * `DROP SEQUENCE` / `DROP FUNCTION` and PostgreSQL no longer rejects the
31
+ * drop with error 2BP01.
22
32
  */
23
33
  export declare function getExecutionPhase(change: Change): Phase;
@@ -1,3 +1,4 @@
1
+ import { AlterTableAlterColumnDropDefault, AlterTableAlterColumnDropIdentity, AlterTableAlterColumnType, } from "../objects/table/changes/table.alter.js";
1
2
  /**
2
3
  * Check if a stable ID represents metadata (ACL, default privileges, comments, etc.)
3
4
  * rather than an actual database object.
@@ -20,6 +21,16 @@ export function isMetadataStableId(stableId) {
20
21
  * - ALTER operations with scope="privilege" → create_alter_object phase (metadata changes)
21
22
  * - ALTER operations that drop actual objects → drop phase (destructive ALTER)
22
23
  * - ALTER operations that don't drop objects → create_alter_object phase (non-destructive ALTER)
24
+ *
25
+ * Dependency-breaking ALTERs that remove a `pg_depend` edge to another
26
+ * object that may be dropped in the same plan (for example
27
+ * `ALTER COLUMN ... DROP DEFAULT` releasing a sequence reference, or
28
+ * `ALTER COLUMN ... TYPE <built-in>` releasing a user-defined type
29
+ * reference) are routed to the drop phase. The drop phase sorts in reverse
30
+ * dependency order using the main catalog, so the catalog edges already
31
+ * in `pg_depend` order the ALTER before any dependent `DROP TYPE` /
32
+ * `DROP SEQUENCE` / `DROP FUNCTION` and PostgreSQL no longer rejects the
33
+ * drop with error 2BP01.
23
34
  */
24
35
  export function getExecutionPhase(change) {
25
36
  // DROP operations always go to drop phase
@@ -43,6 +54,23 @@ export function getExecutionPhase(change) {
43
54
  // Destructive ALTER (DROP COLUMN, DROP CONSTRAINT, etc.) → drop phase
44
55
  return "drop";
45
56
  }
57
+ // Dependency-breaking column ALTERs that release a pg_depend edge.
58
+ // Routing these to the drop phase lets the existing catalog dependency
59
+ // edges (column → sequence, column → identity sequence) order them
60
+ // before the matching DROP statement.
61
+ if (change instanceof AlterTableAlterColumnDropDefault ||
62
+ change instanceof AlterTableAlterColumnDropIdentity) {
63
+ return "drop";
64
+ }
65
+ // ALTER COLUMN ... TYPE only safely runs in the drop phase when the
66
+ // target type is built-in. For user-defined target types we cannot tell
67
+ // here whether the type is created in the same plan, and the create
68
+ // happens in create_alter phase, so we keep the alter in that phase to
69
+ // preserve the create-then-alter ordering.
70
+ if (change instanceof AlterTableAlterColumnType &&
71
+ !change.column.is_custom_type) {
72
+ return "drop";
73
+ }
46
74
  // Non-destructive ALTER (ADD COLUMN, GRANT, etc.) → create_alter phase
47
75
  return "create_alter_object";
48
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/pg-delta",
3
- "version": "1.0.0-alpha.21",
3
+ "version": "1.0.0-alpha.23",
4
4
  "description": "PostgreSQL migrations made easy",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,7 +1,7 @@
1
1
  import debug from "debug";
2
2
  import type { Catalog } from "./catalog.model.ts";
3
3
  import { expandReplaceDependencies } from "./expand-replace-dependencies.ts";
4
- import { normalizePostDiffCycles } from "./post-diff-cycle-breaking.ts";
4
+ import { normalizePostDiffChanges } from "./post-diff-normalization.ts";
5
5
 
6
6
  const debugCatalog = debug("pg-delta:catalog");
7
7
 
@@ -190,6 +190,7 @@ export function diffCatalogs(
190
190
  main.sequences,
191
191
  branch.sequences,
192
192
  branch.tables,
193
+ main.tables,
193
194
  ),
194
195
  );
195
196
  changes.push(...diffTables(diffContext, main.tables, branch.tables));
@@ -238,9 +239,10 @@ export function diffCatalogs(
238
239
  mainCatalog: main,
239
240
  branchCatalog: branch,
240
241
  });
241
- filteredChanges = normalizePostDiffCycles({
242
+ filteredChanges = normalizePostDiffChanges({
242
243
  changes: expandedDependencies.changes,
243
244
  replacedTableIds: expandedDependencies.replacedTableIds,
245
+ branchTables: branch.tables,
244
246
  });
245
247
 
246
248
  debugCatalog(
@@ -267,6 +267,7 @@ export async function createEmptyCatalog(
267
267
  owner: currentUser,
268
268
  comment: "standard public schema",
269
269
  privileges: [],
270
+ security_labels: [],
270
271
  });
271
272
 
272
273
  return new Catalog({
@@ -302,7 +303,19 @@ export async function createEmptyCatalog(
302
303
  });
303
304
  }
304
305
 
305
- export async function extractCatalog(pool: Pool) {
306
+ interface ExtractCatalogOptions {
307
+ /**
308
+ * Number of retry attempts for catalog extractors when `pg_get_*def()`
309
+ * returns NULL for at least one row. See `ExtractRetryOptions.retries`.
310
+ */
311
+ extractRetries?: number;
312
+ }
313
+
314
+ export async function extractCatalog(
315
+ pool: Pool,
316
+ options: ExtractCatalogOptions = {},
317
+ ) {
318
+ const retryOptions = { retries: options.extractRetries };
306
319
  const [
307
320
  aggregates,
308
321
  collations,
@@ -339,21 +352,21 @@ export async function extractCatalog(pool: Pool) {
339
352
  extractDomains(pool).then(listToRecord),
340
353
  extractEnums(pool).then(listToRecord),
341
354
  extractExtensions(pool).then(listToRecord),
342
- extractIndexes(pool).then(listToRecord),
343
- extractMaterializedViews(pool).then(listToRecord),
355
+ extractIndexes(pool, retryOptions).then(listToRecord),
356
+ extractMaterializedViews(pool, retryOptions).then(listToRecord),
344
357
  extractSubscriptions(pool).then(listToRecord),
345
358
  extractPublications(pool).then(listToRecord),
346
- extractProcedures(pool).then(listToRecord),
359
+ extractProcedures(pool, retryOptions).then(listToRecord),
347
360
  extractRlsPolicies(pool).then(listToRecord),
348
361
  extractRoles(pool).then(listToRecord),
349
362
  extractSchemas(pool).then(listToRecord),
350
363
  extractSequences(pool).then(listToRecord),
351
- extractTables(pool).then(listToRecord),
352
- extractTriggers(pool).then(listToRecord),
364
+ extractTables(pool, retryOptions).then(listToRecord),
365
+ extractTriggers(pool, retryOptions).then(listToRecord),
353
366
  extractEventTriggers(pool).then(listToRecord),
354
- extractRules(pool).then(listToRecord),
367
+ extractRules(pool, retryOptions).then(listToRecord),
355
368
  extractRanges(pool).then(listToRecord),
356
- extractViews(pool).then(listToRecord),
369
+ extractViews(pool, retryOptions).then(listToRecord),
357
370
  extractForeignDataWrappers(pool).then(listToRecord),
358
371
  extractServers(pool).then(listToRecord),
359
372
  extractUserMappings(pool).then(listToRecord),
@@ -97,6 +97,137 @@ describe("expandReplaceDependencies", () => {
97
97
  expect(result.replacedTableIds.size).toBe(0);
98
98
  });
99
99
 
100
+ test("promotes surviving dependent view when its referenced table is dropped without a same-name create", async () => {
101
+ // Reproduces issue #228 case 3: ALTER TABLE users RENAME TO members.
102
+ // pg-delta sees `users` as drop-only and `members` as create-only — the
103
+ // stableIds differ, so neither is in the createdIds∩droppedIds replace
104
+ // root set. The dependent view `user_count` exists in both catalogs
105
+ // (its definition was rewritten to FROM members in branch). Without
106
+ // expansion, DROP TABLE users would fail because user_count still
107
+ // references it. The expander must seed the drop-only table as a root
108
+ // so the surviving dependent gets promoted to DROP+CREATE.
109
+ const baseline = await createEmptyCatalog(170000, "postgres");
110
+ const usersTable = new Table({
111
+ schema: "public",
112
+ name: "users",
113
+ persistence: "p",
114
+ row_security: false,
115
+ force_row_security: false,
116
+ has_indexes: false,
117
+ has_rules: false,
118
+ has_triggers: false,
119
+ has_subclasses: false,
120
+ is_populated: true,
121
+ replica_identity: "d",
122
+ is_partition: false,
123
+ options: null,
124
+ partition_bound: null,
125
+ partition_by: null,
126
+ owner: "postgres",
127
+ comment: null,
128
+ parent_schema: null,
129
+ parent_name: null,
130
+ columns: [
131
+ {
132
+ name: "id",
133
+ position: 1,
134
+ data_type: "integer",
135
+ data_type_str: "integer",
136
+ is_custom_type: false,
137
+ custom_type_type: null,
138
+ custom_type_category: null,
139
+ custom_type_schema: null,
140
+ custom_type_name: null,
141
+ not_null: true,
142
+ is_identity: false,
143
+ is_identity_always: false,
144
+ is_generated: false,
145
+ collation: null,
146
+ default: null,
147
+ comment: null,
148
+ },
149
+ ],
150
+ privileges: [],
151
+ });
152
+ const mainView = new View({
153
+ schema: "public",
154
+ name: "user_count",
155
+ owner: "postgres",
156
+ definition: " SELECT count(*) AS n FROM public.users;",
157
+ row_security: false,
158
+ force_row_security: false,
159
+ has_indexes: false,
160
+ has_rules: false,
161
+ has_triggers: false,
162
+ has_subclasses: false,
163
+ is_populated: true,
164
+ replica_identity: "d",
165
+ is_partition: false,
166
+ partition_bound: null,
167
+ comment: null,
168
+ columns: [
169
+ {
170
+ name: "n",
171
+ position: 1,
172
+ data_type: "bigint",
173
+ data_type_str: "bigint",
174
+ is_custom_type: false,
175
+ custom_type_type: null,
176
+ custom_type_category: null,
177
+ custom_type_schema: null,
178
+ custom_type_name: null,
179
+ not_null: false,
180
+ is_identity: false,
181
+ is_identity_always: false,
182
+ is_generated: false,
183
+ collation: null,
184
+ default: null,
185
+ comment: null,
186
+ },
187
+ ],
188
+ options: null,
189
+ privileges: [],
190
+ });
191
+ const branchView = new View({
192
+ ...mainView,
193
+ definition: " SELECT count(*) AS n FROM public.members;",
194
+ });
195
+
196
+ const mainCatalog = new Catalog({
197
+ ...baseline,
198
+ tables: { [usersTable.stableId]: usersTable },
199
+ views: { [mainView.stableId]: mainView },
200
+ depends: [
201
+ {
202
+ dependent_stable_id: mainView.stableId,
203
+ referenced_stable_id: usersTable.stableId,
204
+ deptype: "n",
205
+ },
206
+ ],
207
+ });
208
+ const branchCatalog = new Catalog({
209
+ ...baseline,
210
+ views: { [branchView.stableId]: branchView },
211
+ });
212
+
213
+ // Simulated planner output: DropTable(users) + CreateView orReplace(user_count).
214
+ // The surviving view appears only as a "create" (CREATE OR REPLACE VIEW),
215
+ // never as a drop, so DROP TABLE users would fail without expansion.
216
+ const changes: Change[] = [
217
+ new DropTable({ table: usersTable }),
218
+ new CreateView({ view: branchView, orReplace: true }),
219
+ ];
220
+ const result = expandReplaceDependencies({
221
+ changes,
222
+ mainCatalog,
223
+ branchCatalog,
224
+ });
225
+
226
+ // The view's surviving CREATE OR REPLACE remains, AND a DropView is
227
+ // injected so the drop phase removes the view before the table.
228
+ expect(result.changes.some((c) => c instanceof DropView)).toBe(true);
229
+ });
230
+
100
231
  test("does not replace the owning table for an owned sequence recreation", async () => {
101
232
  const baseline = await createEmptyCatalog(170000, "postgres");
102
233
  // Use `persistence` (UNLOGGED → LOGGED) to trigger the
@@ -129,6 +129,30 @@ export function expandReplaceDependencies({
129
129
  }
130
130
  }
131
131
 
132
+ // Drop-only objects (no matching create — typically a renamed-away table or
133
+ // type) are also expansion roots: anything in main that depends on them via
134
+ // pg_depend must drop before the parent does. Without this seed, a renamed
135
+ // table whose dependent view stays in the branch catalog (with an updated
136
+ // definition that no longer references the old name) would still try to
137
+ // run DROP TABLE old_name while old_name is referenced by the view, which
138
+ // PostgreSQL refuses without CASCADE. The walk below promotes the surviving
139
+ // dependent to DROP+CREATE so its drop is sequenced before the parent drop.
140
+ for (const id of droppedIds) {
141
+ if (createdIds.has(id)) continue;
142
+ if (replaceRoots.has(id)) continue;
143
+ // Only seed for object kinds that can have catalog dependents we know
144
+ // how to recreate via buildReplaceChanges.
145
+ if (
146
+ id.startsWith("table:") ||
147
+ id.startsWith("view:") ||
148
+ id.startsWith("materializedView:") ||
149
+ id.startsWith("type:") ||
150
+ id.startsWith("domain:")
151
+ ) {
152
+ replaceRoots.add(id);
153
+ }
154
+ }
155
+
132
156
  if (replaceRoots.size === 0) {
133
157
  return {
134
158
  changes,
@@ -41,6 +41,16 @@ const membershipChange = {
41
41
  requires: [],
42
42
  } as unknown as Change;
43
43
 
44
+ const securityLabelChange = {
45
+ objectType: "schema",
46
+ operation: "create",
47
+ scope: "security_label",
48
+ schema: { name: "labeled" },
49
+ securityLabel: { provider: "dummy", label: "classified" },
50
+ requires: ["schema:labeled"],
51
+ creates: ["security_label:schema:labeled:dummy"],
52
+ } as unknown as Change;
53
+
44
54
  describe("evaluatePattern", () => {
45
55
  describe("bare key matching (top-level properties)", () => {
46
56
  test("objectType match", () => {
@@ -242,6 +252,23 @@ describe("evaluatePattern", () => {
242
252
  });
243
253
  });
244
254
 
255
+ describe("security label matching", () => {
256
+ test("provider matches security label changes", () => {
257
+ expect(
258
+ evaluatePattern(
259
+ { scope: "security_label", provider: "dummy" },
260
+ securityLabelChange,
261
+ ),
262
+ ).toBe(true);
263
+ expect(
264
+ evaluatePattern(
265
+ { scope: "security_label", provider: "other" },
266
+ securityLabelChange,
267
+ ),
268
+ ).toBe(false);
269
+ });
270
+ });
271
+
245
272
  describe("composition patterns", () => {
246
273
  test("not negates a pattern", () => {
247
274
  expect(
@@ -105,6 +105,22 @@ export function flattenChange(change: Change): Record<string, FlatValue> {
105
105
  flat[`${prefix}/${subKey}`] = flatVal;
106
106
  }
107
107
  }
108
+ } else if (
109
+ key === "securityLabel" &&
110
+ value &&
111
+ typeof value === "object" &&
112
+ !Array.isArray(value)
113
+ ) {
114
+ // Security labels are change-level metadata, so expose provider/label as
115
+ // bare keys for filters like { scope: "security_label", provider: "..." }.
116
+ for (const [subKey, subValue] of Object.entries(
117
+ value as Record<string, unknown>,
118
+ )) {
119
+ const flatVal = toFlatValue(subValue);
120
+ if (flatVal !== undefined) {
121
+ flat[subKey] = flatVal;
122
+ }
123
+ }
108
124
  } else {
109
125
  const flatVal = toFlatValue(value);
110
126
  if (flatVal !== undefined) {
@@ -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 type { Aggregate } from "./aggregate.model.ts";
10
11
  import { AlterAggregateChangeOwner } from "./changes/aggregate.alter.ts";
@@ -19,6 +20,10 @@ import {
19
20
  RevokeAggregatePrivileges,
20
21
  RevokeGrantOptionAggregatePrivileges,
21
22
  } from "./changes/aggregate.privilege.ts";
23
+ import {
24
+ CreateSecurityLabelOnAggregate,
25
+ DropSecurityLabelOnAggregate,
26
+ } from "./changes/aggregate.security-label.ts";
22
27
  import type { AggregateChange } from "./changes/aggregate.types.ts";
23
28
 
24
29
  export function diffAggregates(
@@ -51,6 +56,14 @@ export function diffAggregates(
51
56
  if (aggregate.comment !== null) {
52
57
  changes.push(new CreateCommentOnAggregate({ aggregate }));
53
58
  }
59
+ for (const label of aggregate.security_labels) {
60
+ changes.push(
61
+ new CreateSecurityLabelOnAggregate({
62
+ aggregate,
63
+ securityLabel: label,
64
+ }),
65
+ );
66
+ }
54
67
 
55
68
  // PRIVILEGES: For created objects, compare against default privileges state
56
69
  // The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
@@ -182,6 +195,26 @@ export function diffAggregates(
182
195
  }
183
196
  }
184
197
 
198
+ // SECURITY LABELS
199
+ changes.push(
200
+ ...diffSecurityLabels<
201
+ CreateSecurityLabelOnAggregate | DropSecurityLabelOnAggregate
202
+ >(
203
+ mainAggregate.security_labels,
204
+ branchAggregate.security_labels,
205
+ (securityLabel) =>
206
+ new CreateSecurityLabelOnAggregate({
207
+ aggregate: branchAggregate,
208
+ securityLabel,
209
+ }),
210
+ (securityLabel) =>
211
+ new DropSecurityLabelOnAggregate({
212
+ aggregate: mainAggregate,
213
+ securityLabel,
214
+ }),
215
+ ),
216
+ );
217
+
185
218
  // PRIVILEGES
186
219
  // Filter out PUBLIC's built-in default EXECUTE privilege from main catalog
187
220
  // (PostgreSQL grants it automatically, so we shouldn't compare it)