@supabase/pg-delta 1.0.0-alpha.4 → 1.0.0-alpha.5

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 (359) hide show
  1. package/README.md +40 -23
  2. package/dist/cli/app.js +26 -3
  3. package/dist/cli/bin/cli.js +5 -0
  4. package/dist/cli/commands/catalog-export.d.ts +5 -0
  5. package/dist/cli/commands/catalog-export.js +64 -0
  6. package/dist/cli/commands/declarative-apply.d.ts +6 -0
  7. package/dist/cli/commands/declarative-apply.js +288 -0
  8. package/dist/cli/commands/declarative-export.d.ts +5 -0
  9. package/dist/cli/commands/declarative-export.js +245 -0
  10. package/dist/cli/commands/plan.js +19 -6
  11. package/dist/cli/exit-code.d.ts +2 -0
  12. package/dist/cli/exit-code.js +7 -0
  13. package/dist/cli/formatters/tree/tree.js +3 -2
  14. package/dist/cli/utils/apply-display.d.ts +52 -0
  15. package/dist/cli/utils/apply-display.js +183 -0
  16. package/dist/cli/utils/export-display.d.ts +43 -0
  17. package/dist/cli/utils/export-display.js +202 -0
  18. package/dist/cli/utils/resolve-input.d.ts +7 -0
  19. package/dist/cli/utils/resolve-input.js +13 -0
  20. package/dist/core/catalog-export/index.d.ts +11 -0
  21. package/dist/core/catalog-export/index.js +10 -0
  22. package/dist/core/catalog.diff.d.ts +1 -0
  23. package/dist/core/catalog.diff.js +64 -48
  24. package/dist/core/catalog.model.d.ts +14 -1
  25. package/dist/core/catalog.model.js +103 -1
  26. package/dist/core/catalog.snapshot.d.ts +66 -0
  27. package/dist/core/catalog.snapshot.js +206 -0
  28. package/dist/core/declarative-apply/discover-sql.d.ts +18 -0
  29. package/dist/core/declarative-apply/discover-sql.js +86 -0
  30. package/dist/core/declarative-apply/extract-catalog-providers.d.ts +23 -0
  31. package/dist/core/declarative-apply/extract-catalog-providers.js +159 -0
  32. package/dist/core/declarative-apply/index.d.ts +49 -0
  33. package/dist/core/declarative-apply/index.js +134 -0
  34. package/dist/core/declarative-apply/round-apply.d.ts +100 -0
  35. package/dist/core/declarative-apply/round-apply.js +378 -0
  36. package/dist/core/export/file-mapper.d.ts +71 -0
  37. package/dist/core/export/file-mapper.js +474 -0
  38. package/dist/core/export/grouper.d.ts +13 -0
  39. package/dist/core/export/grouper.js +76 -0
  40. package/dist/core/export/index.d.ts +45 -0
  41. package/dist/core/export/index.js +63 -0
  42. package/dist/core/export/types.d.ts +84 -0
  43. package/dist/core/export/types.js +25 -0
  44. package/dist/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
  45. package/dist/core/integrations/filter/dsl.d.ts +38 -1
  46. package/dist/core/integrations/filter/dsl.js +20 -2
  47. package/dist/core/integrations/filter/extractors.js +42 -0
  48. package/dist/core/integrations/integration-dsl.d.ts +10 -0
  49. package/dist/core/integrations/supabase.d.ts +8 -0
  50. package/dist/core/integrations/supabase.js +9 -0
  51. package/dist/core/objects/aggregate/aggregate.diff.d.ts +2 -8
  52. package/dist/core/objects/aggregate/aggregate.diff.js +16 -70
  53. package/dist/core/objects/aggregate/aggregate.model.d.ts +8 -8
  54. package/dist/core/objects/aggregate/aggregate.model.js +1 -1
  55. package/dist/core/objects/aggregate/changes/aggregate.create.js +1 -1
  56. package/dist/core/objects/aggregate/changes/aggregate.drop.js +1 -1
  57. package/dist/core/objects/base.privilege-diff.d.ts +38 -13
  58. package/dist/core/objects/base.privilege-diff.js +104 -22
  59. package/dist/core/objects/base.privilege.d.ts +1 -0
  60. package/dist/core/objects/base.privilege.js +9 -2
  61. package/dist/core/objects/collation/collation.diff.d.ts +2 -3
  62. package/dist/core/objects/diff-context.d.ts +15 -0
  63. package/dist/core/objects/diff-context.js +1 -0
  64. package/dist/core/objects/domain/changes/domain.create.js +4 -2
  65. package/dist/core/objects/domain/domain.diff.d.ts +2 -8
  66. package/dist/core/objects/domain/domain.diff.js +16 -77
  67. package/dist/core/objects/domain/domain.model.js +1 -1
  68. package/dist/core/objects/event-trigger/event-trigger.diff.d.ts +2 -3
  69. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.d.ts +2 -8
  70. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.js +13 -77
  71. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.js +2 -2
  72. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.d.ts +2 -8
  73. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -77
  74. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +1 -1
  75. package/dist/core/objects/foreign-data-wrapper/server/server.diff.d.ts +2 -8
  76. package/dist/core/objects/foreign-data-wrapper/server/server.diff.js +13 -77
  77. package/dist/core/objects/language/language.diff.d.ts +2 -5
  78. package/dist/core/objects/language/language.diff.js +7 -39
  79. package/dist/core/objects/materialized-view/materialized-view.diff.d.ts +2 -8
  80. package/dist/core/objects/materialized-view/materialized-view.diff.js +16 -158
  81. package/dist/core/objects/materialized-view/materialized-view.model.d.ts +3 -3
  82. package/dist/core/objects/materialized-view/materialized-view.model.js +1 -1
  83. package/dist/core/objects/procedure/changes/procedure.alter.js +12 -12
  84. package/dist/core/objects/procedure/procedure.diff.d.ts +2 -8
  85. package/dist/core/objects/procedure/procedure.diff.js +16 -77
  86. package/dist/core/objects/procedure/procedure.model.d.ts +9 -9
  87. package/dist/core/objects/procedure/procedure.model.js +1 -1
  88. package/dist/core/objects/publication/changes/publication.alter.d.ts +0 -9
  89. package/dist/core/objects/publication/changes/publication.alter.js +0 -14
  90. package/dist/core/objects/publication/changes/publication.types.d.ts +2 -2
  91. package/dist/core/objects/publication/publication.diff.d.ts +2 -3
  92. package/dist/core/objects/publication/publication.diff.js +8 -13
  93. package/dist/core/objects/rls-policy/changes/rls-policy.alter.js +3 -3
  94. package/dist/core/objects/rls-policy/rls-policy.model.d.ts +2 -2
  95. package/dist/core/objects/role/role.diff.js +22 -1
  96. package/dist/core/objects/role/role.model.d.ts +4 -3
  97. package/dist/core/objects/role/role.model.js +118 -12
  98. package/dist/core/objects/rule/rule.model.d.ts +1 -1
  99. package/dist/core/objects/schema/schema.diff.d.ts +2 -8
  100. package/dist/core/objects/schema/schema.diff.js +16 -77
  101. package/dist/core/objects/schema/schema.model.js +1 -1
  102. package/dist/core/objects/sequence/sequence.diff.d.ts +2 -8
  103. package/dist/core/objects/sequence/sequence.diff.js +16 -79
  104. package/dist/core/objects/sequence/sequence.model.js +1 -1
  105. package/dist/core/objects/subscription/subscription.diff.d.ts +2 -3
  106. package/dist/core/objects/table/changes/table.create.js +3 -0
  107. package/dist/core/objects/table/table.diff.d.ts +2 -8
  108. package/dist/core/objects/table/table.diff.js +26 -157
  109. package/dist/core/objects/table/table.model.d.ts +23 -22
  110. package/dist/core/objects/table/table.model.js +1 -1
  111. package/dist/core/objects/trigger/changes/trigger.create.js +2 -4
  112. package/dist/core/objects/trigger/trigger.model.d.ts +8 -0
  113. package/dist/core/objects/trigger/trigger.model.js +11 -0
  114. package/dist/core/objects/type/composite-type/composite-type.diff.d.ts +2 -8
  115. package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -77
  116. package/dist/core/objects/type/composite-type/composite-type.model.d.ts +3 -3
  117. package/dist/core/objects/type/composite-type/composite-type.model.js +2 -1
  118. package/dist/core/objects/type/enum/enum.diff.d.ts +2 -8
  119. package/dist/core/objects/type/enum/enum.diff.js +25 -112
  120. package/dist/core/objects/type/enum/enum.model.js +1 -1
  121. package/dist/core/objects/type/range/changes/range.create.js +6 -3
  122. package/dist/core/objects/type/range/range.diff.d.ts +2 -8
  123. package/dist/core/objects/type/range/range.diff.js +16 -77
  124. package/dist/core/objects/type/range/range.model.js +1 -1
  125. package/dist/core/objects/view/view.diff.d.ts +2 -8
  126. package/dist/core/objects/view/view.diff.js +16 -158
  127. package/dist/core/objects/view/view.model.d.ts +18 -4
  128. package/dist/core/objects/view/view.model.js +3 -13
  129. package/dist/core/plan/apply.js +9 -26
  130. package/dist/core/plan/create.d.ts +19 -6
  131. package/dist/core/plan/create.js +134 -174
  132. package/dist/core/plan/serialize.js +16 -4
  133. package/dist/core/plan/sql-format/fixtures.js +3 -5
  134. package/dist/core/plan/sql-format/keyword-case.js +26 -1
  135. package/dist/core/plan/ssl-config.d.ts +32 -0
  136. package/dist/core/plan/ssl-config.js +115 -0
  137. package/dist/core/plan/types.d.ts +6 -0
  138. package/dist/core/postgres-config.d.ts +14 -0
  139. package/dist/core/postgres-config.js +53 -2
  140. package/dist/core/sort/graph-builder.js +10 -0
  141. package/dist/core/sort/logical-sort.js +31 -23
  142. package/dist/core/test-utils/assert-valid-sql.d.ts +10 -0
  143. package/dist/core/test-utils/assert-valid-sql.js +19 -0
  144. package/dist/index.d.ts +6 -0
  145. package/dist/index.js +6 -1
  146. package/package.json +21 -4
  147. package/src/cli/app.ts +27 -3
  148. package/src/cli/bin/cli.ts +6 -0
  149. package/src/cli/commands/catalog-export.ts +78 -0
  150. package/src/cli/commands/declarative-apply.diagnostics.test.ts +77 -0
  151. package/src/cli/commands/declarative-apply.ts +380 -0
  152. package/src/cli/commands/declarative-export.ts +330 -0
  153. package/src/cli/commands/plan.ts +28 -7
  154. package/src/cli/exit-code.test.ts +19 -0
  155. package/src/cli/exit-code.ts +7 -0
  156. package/src/cli/formatters/tree/tree.ts +3 -2
  157. package/src/cli/utils/apply-display.test.ts +348 -0
  158. package/src/cli/utils/apply-display.ts +238 -0
  159. package/src/cli/utils/export-display.test.ts +103 -0
  160. package/src/cli/utils/export-display.ts +275 -0
  161. package/src/cli/utils/integrations.test.ts +44 -0
  162. package/src/cli/utils/resolve-input.test.ts +38 -0
  163. package/src/cli/utils/resolve-input.ts +17 -0
  164. package/src/core/catalog-export/index.ts +20 -0
  165. package/src/core/catalog.diff.ts +79 -78
  166. package/src/core/catalog.model.test.ts +122 -0
  167. package/src/core/catalog.model.ts +127 -1
  168. package/src/core/catalog.snapshot.test.ts +464 -0
  169. package/src/core/catalog.snapshot.ts +289 -0
  170. package/src/core/declarative-apply/discover-sql.test.ts +103 -0
  171. package/src/core/declarative-apply/discover-sql.ts +107 -0
  172. package/src/core/declarative-apply/extract-catalog-providers.ts +220 -0
  173. package/src/core/declarative-apply/index.test.ts +67 -0
  174. package/src/core/declarative-apply/index.ts +205 -0
  175. package/src/core/declarative-apply/round-apply.test.ts +504 -0
  176. package/src/core/declarative-apply/round-apply.ts +562 -0
  177. package/src/core/expand-replace-dependencies.test.ts +70 -0
  178. package/src/core/export/file-mapper.test.ts +816 -0
  179. package/src/core/export/file-mapper.ts +574 -0
  180. package/src/core/export/grouper.ts +108 -0
  181. package/src/core/export/index.ts +129 -0
  182. package/src/core/export/types.ts +104 -0
  183. package/src/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
  184. package/src/core/integrations/filter/dsl.test.ts +211 -0
  185. package/src/core/integrations/filter/dsl.ts +65 -3
  186. package/src/core/integrations/filter/extractors.test.ts +244 -0
  187. package/src/core/integrations/filter/extractors.ts +42 -0
  188. package/src/core/integrations/integration-dsl.ts +10 -0
  189. package/src/core/integrations/serialize/dsl.test.ts +91 -0
  190. package/src/core/integrations/supabase.ts +9 -0
  191. package/src/core/objects/aggregate/aggregate.diff.ts +39 -95
  192. package/src/core/objects/aggregate/aggregate.model.ts +1 -1
  193. package/src/core/objects/aggregate/changes/aggregate.alter.test.ts +3 -1
  194. package/src/core/objects/aggregate/changes/aggregate.comment.test.ts +5 -2
  195. package/src/core/objects/aggregate/changes/aggregate.create.test.ts +6 -3
  196. package/src/core/objects/aggregate/changes/aggregate.create.ts +1 -1
  197. package/src/core/objects/aggregate/changes/aggregate.drop.test.ts +7 -3
  198. package/src/core/objects/aggregate/changes/aggregate.drop.ts +1 -1
  199. package/src/core/objects/aggregate/changes/aggregate.privilege.test.ts +9 -3
  200. package/src/core/objects/base.privilege-diff.ts +178 -30
  201. package/src/core/objects/base.privilege.ts +9 -2
  202. package/src/core/objects/collation/changes/collation.alter.test.ts +7 -2
  203. package/src/core/objects/collation/changes/collation.create.test.ts +7 -2
  204. package/src/core/objects/collation/changes/collation.drop.test.ts +4 -1
  205. package/src/core/objects/collation/collation.diff.test.ts +9 -12
  206. package/src/core/objects/collation/collation.diff.ts +2 -1
  207. package/src/core/objects/diff-context.ts +16 -0
  208. package/src/core/objects/domain/changes/domain.alter.test.ts +28 -9
  209. package/src/core/objects/domain/changes/domain.create.test.ts +32 -2
  210. package/src/core/objects/domain/changes/domain.create.ts +7 -1
  211. package/src/core/objects/domain/changes/domain.drop.test.ts +4 -1
  212. package/src/core/objects/domain/domain.diff.ts +39 -102
  213. package/src/core/objects/domain/domain.model.ts +1 -1
  214. package/src/core/objects/event-trigger/changes/event-trigger.alter.test.ts +10 -3
  215. package/src/core/objects/event-trigger/changes/event-trigger.create.test.ts +4 -1
  216. package/src/core/objects/event-trigger/changes/event-trigger.drop.test.ts +4 -1
  217. package/src/core/objects/event-trigger/event-trigger.diff.test.ts +12 -7
  218. package/src/core/objects/event-trigger/event-trigger.diff.ts +2 -1
  219. package/src/core/objects/extension/changes/extension.alter.test.ts +7 -2
  220. package/src/core/objects/extension/changes/extension.create.test.ts +4 -1
  221. package/src/core/objects/extension/changes/extension.drop.test.ts +4 -1
  222. package/src/core/objects/extension/extension.model.test.ts +98 -0
  223. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.test.ts +16 -5
  224. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.test.ts +51 -16
  225. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.drop.test.ts +4 -1
  226. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.test.ts +111 -4
  227. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.ts +31 -101
  228. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts +2 -2
  229. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.test.ts +46 -15
  230. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.test.ts +13 -4
  231. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.drop.test.ts +4 -1
  232. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +39 -102
  233. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +1 -1
  234. package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.test.ts +22 -7
  235. package/src/core/objects/foreign-data-wrapper/server/changes/server.create.test.ts +19 -6
  236. package/src/core/objects/foreign-data-wrapper/server/changes/server.drop.test.ts +4 -1
  237. package/src/core/objects/foreign-data-wrapper/server/server.diff.test.ts +95 -0
  238. package/src/core/objects/foreign-data-wrapper/server/server.diff.ts +31 -101
  239. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts +13 -4
  240. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts +16 -5
  241. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.drop.test.ts +10 -3
  242. package/src/core/objects/index/changes/index.alter.test.ts +13 -4
  243. package/src/core/objects/index/changes/index.create.test.ts +4 -1
  244. package/src/core/objects/index/changes/index.drop.test.ts +4 -1
  245. package/src/core/objects/language/changes/language.alter.test.ts +4 -1
  246. package/src/core/objects/language/changes/language.create.test.ts +4 -1
  247. package/src/core/objects/language/changes/language.drop.test.ts +4 -1
  248. package/src/core/objects/language/language.diff.test.ts +86 -4
  249. package/src/core/objects/language/language.diff.ts +17 -49
  250. package/src/core/objects/materialized-view/changes/materialized-view.alter.test.ts +10 -3
  251. package/src/core/objects/materialized-view/changes/materialized-view.create.test.ts +7 -2
  252. package/src/core/objects/materialized-view/changes/materialized-view.drop.test.ts +4 -1
  253. package/src/core/objects/materialized-view/materialized-view.diff.test.ts +162 -0
  254. package/src/core/objects/materialized-view/materialized-view.diff.ts +41 -191
  255. package/src/core/objects/materialized-view/materialized-view.model.ts +1 -1
  256. package/src/core/objects/procedure/changes/procedure.alter.test.ts +121 -49
  257. package/src/core/objects/procedure/changes/procedure.alter.ts +15 -12
  258. package/src/core/objects/procedure/changes/procedure.create.test.ts +4 -1
  259. package/src/core/objects/procedure/changes/procedure.drop.test.ts +7 -2
  260. package/src/core/objects/procedure/procedure.diff.ts +39 -102
  261. package/src/core/objects/procedure/procedure.model.ts +1 -1
  262. package/src/core/objects/publication/changes/publication.alter.test.ts +15 -21
  263. package/src/core/objects/publication/changes/publication.alter.ts +0 -18
  264. package/src/core/objects/publication/changes/publication.comment.test.ts +5 -2
  265. package/src/core/objects/publication/changes/publication.create.test.ts +5 -2
  266. package/src/core/objects/publication/changes/publication.drop.test.ts +3 -1
  267. package/src/core/objects/publication/changes/publication.types.ts +0 -2
  268. package/src/core/objects/publication/publication.diff.test.ts +24 -19
  269. package/src/core/objects/publication/publication.diff.ts +9 -15
  270. package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +31 -14
  271. package/src/core/objects/rls-policy/changes/rls-policy.alter.ts +3 -3
  272. package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +10 -3
  273. package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +4 -1
  274. package/src/core/objects/role/changes/role.alter.test.ts +31 -15
  275. package/src/core/objects/role/changes/role.create.test.ts +6 -2
  276. package/src/core/objects/role/changes/role.drop.test.ts +4 -1
  277. package/src/core/objects/role/role.diff.test.ts +235 -0
  278. package/src/core/objects/role/role.diff.ts +21 -1
  279. package/src/core/objects/role/role.model.ts +122 -14
  280. package/src/core/objects/rule/changes/rule.alter.test.ts +7 -3
  281. package/src/core/objects/rule/changes/rule.comment.test.ts +5 -2
  282. package/src/core/objects/rule/changes/rule.create.test.ts +6 -2
  283. package/src/core/objects/rule/changes/rule.drop.test.ts +3 -1
  284. package/src/core/objects/schema/changes/schema.alter.test.ts +4 -1
  285. package/src/core/objects/schema/changes/schema.create.test.ts +4 -1
  286. package/src/core/objects/schema/changes/schema.drop.test.ts +4 -1
  287. package/src/core/objects/schema/schema.diff.ts +39 -102
  288. package/src/core/objects/schema/schema.model.ts +1 -1
  289. package/src/core/objects/sequence/changes/sequence.alter.test.ts +11 -5
  290. package/src/core/objects/sequence/changes/sequence.create.test.ts +8 -3
  291. package/src/core/objects/sequence/changes/sequence.drop.test.ts +4 -1
  292. package/src/core/objects/sequence/sequence.diff.test.ts +114 -0
  293. package/src/core/objects/sequence/sequence.diff.ts +39 -104
  294. package/src/core/objects/sequence/sequence.model.ts +1 -1
  295. package/src/core/objects/subscription/changes/subscription.alter.test.ts +15 -5
  296. package/src/core/objects/subscription/changes/subscription.comment.test.ts +5 -2
  297. package/src/core/objects/subscription/changes/subscription.create.test.ts +5 -2
  298. package/src/core/objects/subscription/changes/subscription.drop.test.ts +3 -1
  299. package/src/core/objects/subscription/subscription.diff.test.ts +16 -11
  300. package/src/core/objects/subscription/subscription.diff.ts +2 -1
  301. package/src/core/objects/table/changes/table.alter.test.ts +38 -15
  302. package/src/core/objects/table/changes/table.create.test.ts +41 -3
  303. package/src/core/objects/table/changes/table.create.ts +4 -0
  304. package/src/core/objects/table/changes/table.drop.test.ts +3 -1
  305. package/src/core/objects/table/table.diff.test.ts +157 -0
  306. package/src/core/objects/table/table.diff.ts +54 -190
  307. package/src/core/objects/table/table.model.ts +1 -1
  308. package/src/core/objects/trigger/changes/trigger.alter.test.ts +8 -4
  309. package/src/core/objects/trigger/changes/trigger.create.test.ts +5 -1
  310. package/src/core/objects/trigger/changes/trigger.create.ts +7 -4
  311. package/src/core/objects/trigger/changes/trigger.drop.test.ts +5 -1
  312. package/src/core/objects/trigger/trigger.diff.test.ts +1 -0
  313. package/src/core/objects/trigger/trigger.model.ts +12 -0
  314. package/src/core/objects/type/composite-type/changes/composite-type.alter.test.ts +10 -4
  315. package/src/core/objects/type/composite-type/changes/composite-type.create.test.ts +7 -2
  316. package/src/core/objects/type/composite-type/changes/composite-type.drop.test.ts +4 -1
  317. package/src/core/objects/type/composite-type/composite-type.diff.test.ts +78 -0
  318. package/src/core/objects/type/composite-type/composite-type.diff.ts +39 -101
  319. package/src/core/objects/type/composite-type/composite-type.model.ts +2 -1
  320. package/src/core/objects/type/enum/changes/enum.alter.test.ts +14 -5
  321. package/src/core/objects/type/enum/changes/enum.create.test.ts +4 -1
  322. package/src/core/objects/type/enum/changes/enum.drop.test.ts +4 -1
  323. package/src/core/objects/type/enum/enum.diff.test.ts +181 -0
  324. package/src/core/objects/type/enum/enum.diff.ts +58 -146
  325. package/src/core/objects/type/enum/enum.model.ts +1 -1
  326. package/src/core/objects/type/range/changes/range.alter.test.ts +3 -1
  327. package/src/core/objects/type/range/changes/range.create.test.ts +5 -2
  328. package/src/core/objects/type/range/changes/range.create.ts +6 -2
  329. package/src/core/objects/type/range/changes/range.drop.test.ts +3 -1
  330. package/src/core/objects/type/range/range.diff.test.ts +77 -0
  331. package/src/core/objects/type/range/range.diff.ts +39 -101
  332. package/src/core/objects/type/range/range.model.ts +1 -1
  333. package/src/core/objects/view/changes/view.alter.test.ts +8 -3
  334. package/src/core/objects/view/changes/view.create.test.ts +7 -2
  335. package/src/core/objects/view/changes/view.drop.test.ts +4 -1
  336. package/src/core/objects/view/view.diff.test.ts +82 -0
  337. package/src/core/objects/view/view.diff.ts +41 -191
  338. package/src/core/objects/view/view.model.ts +3 -17
  339. package/src/core/plan/apply.ts +9 -27
  340. package/src/core/plan/create.ts +173 -237
  341. package/src/core/plan/serialize.test.ts +317 -0
  342. package/src/core/plan/serialize.ts +18 -4
  343. package/src/core/plan/sql-format/fixtures.ts +2 -5
  344. package/src/core/plan/sql-format/format-lowercase-coverage.test.ts +52 -0
  345. package/src/core/plan/sql-format/format-off.test.ts +14 -17
  346. package/src/core/plan/sql-format/format-pretty-lower-leading.test.ts +27 -22
  347. package/src/core/plan/sql-format/format-pretty-narrow.test.ts +17 -21
  348. package/src/core/plan/sql-format/format-pretty-preserve.test.ts +25 -20
  349. package/src/core/plan/sql-format/format-pretty-upper.test.ts +23 -20
  350. package/src/core/plan/sql-format/keyword-case.ts +36 -1
  351. package/src/core/plan/ssl-config.ts +172 -0
  352. package/src/core/plan/types.ts +6 -0
  353. package/src/core/postgres-config.ts +71 -2
  354. package/src/core/sort/graph-builder.ts +12 -0
  355. package/src/core/sort/logical-sort.test.ts +371 -0
  356. package/src/core/sort/logical-sort.ts +32 -25
  357. package/src/core/sort/topological-sort.test.ts +275 -0
  358. package/src/core/test-utils/assert-valid-sql.ts +20 -0
  359. package/src/index.ts +26 -2
@@ -14,10 +14,10 @@ declare const viewPropsSchema: z.ZodObject<{
14
14
  has_subclasses: z.ZodBoolean;
15
15
  is_populated: z.ZodBoolean;
16
16
  replica_identity: z.ZodEnum<{
17
- f: "f";
18
17
  n: "n";
19
18
  i: "i";
20
19
  d: "d";
20
+ f: "f";
21
21
  }>;
22
22
  is_partition: z.ZodBoolean;
23
23
  options: z.ZodNullable<z.ZodArray<z.ZodString>>;
@@ -85,7 +85,7 @@ export declare class View extends BasePgModel implements TableLikeObject {
85
85
  has_triggers: boolean;
86
86
  has_subclasses: boolean;
87
87
  is_populated: boolean;
88
- replica_identity: "f" | "n" | "i" | "d";
88
+ replica_identity: "n" | "i" | "d" | "f";
89
89
  is_partition: boolean;
90
90
  options: string[] | null;
91
91
  partition_bound: string | null;
@@ -123,7 +123,21 @@ export declare class View extends BasePgModel implements TableLikeObject {
123
123
  };
124
124
  data: {
125
125
  columns: {
126
- [x: string]: unknown;
126
+ name: string;
127
+ data_type: string;
128
+ data_type_str: string;
129
+ is_custom_type: boolean;
130
+ custom_type_type: string | null;
131
+ custom_type_category: string | null;
132
+ custom_type_schema: string | null;
133
+ custom_type_name: string | null;
134
+ not_null: boolean;
135
+ is_identity: boolean;
136
+ is_identity_always: boolean;
137
+ is_generated: boolean;
138
+ collation: string | null;
139
+ default: string | null;
140
+ comment: string | null;
127
141
  }[];
128
142
  definition: string;
129
143
  row_security: boolean;
@@ -133,7 +147,7 @@ export declare class View extends BasePgModel implements TableLikeObject {
133
147
  has_triggers: boolean;
134
148
  has_subclasses: boolean;
135
149
  is_populated: boolean;
136
- replica_identity: "f" | "n" | "i" | "d";
150
+ replica_identity: "n" | "i" | "d" | "f";
137
151
  is_partition: boolean;
138
152
  options: string[] | null;
139
153
  partition_bound: string | null;
@@ -1,6 +1,6 @@
1
1
  import { sql } from "@ts-safeql/sql-tag";
2
2
  import z from "zod";
3
- import { BasePgModel, columnPropsSchema, } from "../base.model.js";
3
+ import { BasePgModel, columnPropsSchema, normalizeColumns, } from "../base.model.js";
4
4
  import { privilegePropsSchema, } from "../base.privilege-diff.js";
5
5
  import { ReplicaIdentitySchema } from "../table/table.model.js";
6
6
  const viewPropsSchema = z.object({
@@ -95,21 +95,11 @@ export class View extends BasePgModel {
95
95
  };
96
96
  }
97
97
  stableSnapshot() {
98
- const normalizeColumns = () => [...this.columns]
99
- .map((col) => {
100
- const { position: _pos, ...rest } = col;
101
- return rest;
102
- })
103
- .sort((a, b) => {
104
- const nameA = a.name ?? "";
105
- const nameB = b.name ?? "";
106
- return nameA.localeCompare(nameB);
107
- });
108
98
  return {
109
99
  identity: this.identityFields,
110
100
  data: {
111
101
  ...this.dataFields,
112
- columns: normalizeColumns(),
102
+ columns: normalizeColumns(this.columns),
113
103
  },
114
104
  };
115
105
  }
@@ -218,7 +208,7 @@ select
218
208
  from (
219
209
  -- one row for object ACL + one row per column ACL
220
210
  select null::name as attname, v.oid as relacl_oid, (
221
- select c_rel.relacl from pg_class c_rel where c_rel.oid = v.oid
211
+ select COALESCE(c_rel.relacl, acldefault('r', c_rel.relowner)) from pg_class c_rel where c_rel.oid = v.oid
222
212
  ) as acl
223
213
  union all
224
214
  select a2.attname, v.oid as relacl_oid, a2.attacl
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Plan application - execute migration plans against target databases.
3
3
  */
4
- import { escapeIdentifier } from "pg";
5
4
  import { diffCatalogs } from "../catalog.diff.js";
6
5
  import { extractCatalog } from "../catalog.model.js";
7
6
  import { buildPlanScopeFingerprint, hashStableIds } from "../fingerprint.js";
8
7
  import { compileFilterDSL } from "../integrations/filter/dsl.js";
9
- import { createPool, endPool } from "../postgres-config.js";
8
+ import { createManagedPool, endPool } from "../postgres-config.js";
10
9
  import { sortChanges } from "../sort/sort-changes.js";
11
10
  /**
12
11
  * Check if a statement is a session configuration statement (standalone SET statements).
@@ -30,39 +29,23 @@ export async function applyPlan(plan, source, target, options = {}) {
30
29
  let desiredPool;
31
30
  let shouldCloseCurrent = false;
32
31
  let shouldCloseDesired = false;
33
- // Suppress expected shutdown errors from idle pool connections (57P01 = admin_shutdown)
34
- const onError = (err) => {
35
- if (err.code !== "57P01") {
36
- console.error("Pool error:", err);
37
- }
38
- };
39
32
  if (typeof source === "string") {
40
- currentPool = createPool(source, {
41
- onError,
42
- onConnect: async (client) => {
43
- // Force fully qualified names in catalog queries for fingerprint verification
44
- await client.query("SET search_path = ''");
45
- if (plan.role) {
46
- await client.query(`SET ROLE ${escapeIdentifier(plan.role)}`);
47
- }
48
- },
33
+ const managed = await createManagedPool(source, {
34
+ role: plan.role,
35
+ label: "source",
49
36
  });
37
+ currentPool = managed.pool;
50
38
  shouldCloseCurrent = true;
51
39
  }
52
40
  else {
53
41
  currentPool = source;
54
42
  }
55
43
  if (typeof target === "string") {
56
- desiredPool = createPool(target, {
57
- onError,
58
- onConnect: async (client) => {
59
- // Force fully qualified names in catalog queries for fingerprint verification
60
- await client.query("SET search_path = ''");
61
- if (plan.role) {
62
- await client.query(`SET ROLE ${escapeIdentifier(plan.role)}`);
63
- }
64
- },
44
+ const managed = await createManagedPool(target, {
45
+ role: plan.role,
46
+ label: "target",
65
47
  });
48
+ desiredPool = managed.pool;
66
49
  shouldCloseDesired = true;
67
50
  }
68
51
  else {
@@ -2,21 +2,34 @@
2
2
  * Plan creation - the main entry point for creating migration plans.
3
3
  */
4
4
  import type { Pool } from "pg";
5
+ import { Catalog } from "../catalog.model.ts";
5
6
  import type { Change } from "../change.types.ts";
6
7
  import type { DiffContext } from "../context.ts";
7
8
  import type { CreatePlanOptions, Plan } from "./types.ts";
8
9
  /**
9
- * Create a migration plan by comparing two databases.
10
+ * Input for source/target: a postgres connection URL, an existing Pool, or
11
+ * an already-resolved Catalog (e.g. deserialized from a snapshot file).
12
+ */
13
+ export type CatalogInput = string | Pool | Catalog;
14
+ /**
15
+ * Create a migration plan by comparing two catalog states.
16
+ *
17
+ * Each input can be:
18
+ * - A postgres connection URL (string) -- a pool is created and catalog extracted
19
+ * - An existing pg Pool -- catalog is extracted directly
20
+ * - A Catalog instance -- used as-is (e.g. from a deserialized snapshot)
21
+ *
22
+ * When `source` is `null`, a minimal empty catalog (`createEmptyCatalog`) is
23
+ * used as the baseline. For a more accurate baseline, pass a Catalog
24
+ * deserialized from a snapshot of `template1` or another reference database.
10
25
  *
11
- * @param fromUrl - Source database connection URL (current state)
12
- * @param toUrl - Target database connection URL (desired state)
26
+ * @param source - Source catalog input (current state), or null for empty baseline
27
+ * @param target - Target catalog input (desired state)
13
28
  * @param options - Optional configuration
14
29
  * @returns A Plan if there are changes, null if databases are identical
15
30
  */
16
- type ConnectionInput = string | Pool;
17
- export declare function createPlan(source: ConnectionInput, target: ConnectionInput, options?: CreatePlanOptions): Promise<{
31
+ export declare function createPlan(source: CatalogInput | null, target: CatalogInput, options?: CreatePlanOptions): Promise<{
18
32
  plan: Plan;
19
33
  sortedChanges: Change[];
20
34
  ctx: DiffContext;
21
35
  } | null>;
22
- export {};
@@ -1,196 +1,68 @@
1
1
  /**
2
2
  * Plan creation - the main entry point for creating migration plans.
3
3
  */
4
- import { readFile } from "node:fs/promises";
5
4
  import { escapeIdentifier } from "pg";
6
5
  import { diffCatalogs } from "../catalog.diff.js";
7
- import { extractCatalog } from "../catalog.model.js";
6
+ import { Catalog, createEmptyCatalog, extractCatalog, } from "../catalog.model.js";
8
7
  import { buildPlanScopeFingerprint, hashStableIds } from "../fingerprint.js";
9
8
  import { compileFilterDSL, } from "../integrations/filter/dsl.js";
10
9
  import { compileSerializeDSL, } from "../integrations/serialize/dsl.js";
11
- import { createPool, endPool } from "../postgres-config.js";
10
+ import { createManagedPool, endPool } from "../postgres-config.js";
12
11
  import { sortChanges } from "../sort/sort-changes.js";
13
12
  import { classifyChangesRisk } from "./risk.js";
14
13
  /**
15
- * Parse SSL configuration from a PostgreSQL connection URL.
16
- * Supports sslmode (require, verify-ca, verify-full, prefer, disable).
17
- * Certificates can be provided via:
18
- * - Query string parameters (file paths): sslrootcert, sslcert, sslkey (preferred)
19
- * - Environment variables (content): PGDELTA_SOURCE_SSLROOTCERT/SSLCERT/SSLKEY or PGDELTA_TARGET_SSLROOTCERT/SSLCERT/SSLKEY
20
- * Returns SSL options for the postgres.js library and a cleaned URL without SSL-related query parameters.
14
+ * Create a migration plan by comparing two catalog states.
15
+ *
16
+ * Each input can be:
17
+ * - A postgres connection URL (string) -- a pool is created and catalog extracted
18
+ * - An existing pg Pool -- catalog is extracted directly
19
+ * - A Catalog instance -- used as-is (e.g. from a deserialized snapshot)
20
+ *
21
+ * When `source` is `null`, a minimal empty catalog (`createEmptyCatalog`) is
22
+ * used as the baseline. For a more accurate baseline, pass a Catalog
23
+ * deserialized from a snapshot of `template1` or another reference database.
24
+ *
25
+ * @param source - Source catalog input (current state), or null for empty baseline
26
+ * @param target - Target catalog input (desired state)
27
+ * @param options - Optional configuration
28
+ * @returns A Plan if there are changes, null if databases are identical
21
29
  */
22
- async function parseSslConfig(url, connectionType) {
23
- const urlObj = new URL(url);
24
- const sslmode = urlObj.searchParams.get("sslmode");
25
- const sslrootcert = urlObj.searchParams.get("sslrootcert");
26
- const sslcert = urlObj.searchParams.get("sslcert");
27
- const sslkey = urlObj.searchParams.get("sslkey");
28
- // Remove SSL-related query parameters since we parse them ourselves
29
- urlObj.searchParams.delete("sslmode");
30
- urlObj.searchParams.delete("sslrootcert");
31
- urlObj.searchParams.delete("sslcert");
32
- urlObj.searchParams.delete("sslkey");
33
- const cleanedUrl = urlObj.toString();
34
- // Handle different SSL modes
35
- if (sslmode === "disable") {
36
- return { cleanedUrl, ssl: false };
37
- }
38
- if (sslmode === "require" ||
39
- sslmode === "prefer" ||
40
- sslmode === "verify-ca" ||
41
- sslmode === "verify-full") {
42
- // Helper function to get certificate value: query param (file path) takes precedence over env var (content)
43
- const getCertValue = async (queryParam, envVarName) => {
44
- // Prefer query parameter (file path)
45
- if (queryParam) {
46
- try {
47
- return await readFile(queryParam, "utf-8");
48
- }
49
- catch (error) {
50
- throw new Error(`Failed to read certificate file '${queryParam}': ${error instanceof Error ? error.message : String(error)}`);
51
- }
52
- }
53
- // Fallback to environment variable (content)
54
- const envValue = process.env[envVarName];
55
- return envValue || undefined;
56
- };
57
- const hasExplicitVerification = sslmode === "verify-ca" || sslmode === "verify-full";
58
- // Get CA certificate value.
59
- // - verify-ca/verify-full: check query param first, then env var
60
- // - require/prefer: only check query param (libpq backward compatibility
61
- // requires an explicit root CA *file*, not a global env var)
62
- const caEnvVar = connectionType === "source"
63
- ? "PGDELTA_SOURCE_SSLROOTCERT"
64
- : "PGDELTA_TARGET_SSLROOTCERT";
65
- let caValue;
66
- if (sslrootcert) {
67
- // Explicit file path in query param — always honour it
68
- caValue = await getCertValue(sslrootcert, caEnvVar);
69
- }
70
- else if (hasExplicitVerification) {
71
- // verify-ca / verify-full without file path — fall back to env var
72
- caValue = await getCertValue(null, caEnvVar);
73
- }
74
- // require/prefer without sslrootcert: no CA cert, no verification
75
- // Determine if we should verify the CA chain
76
- // From PostgreSQL docs: "if a root CA file exists, the behavior of sslmode=require
77
- // will be the same as that of verify-ca"
78
- const hasLibpqCompatibility = (sslmode === "require" || sslmode === "prefer") && caValue !== undefined;
79
- const shouldVerifyCa = hasExplicitVerification || hasLibpqCompatibility;
80
- // Determine if we should verify hostname
81
- // - verify-full: verify both CA and hostname
82
- // - verify-ca: verify CA only (skip hostname)
83
- // - require/prefer with CA (libpq compat): behaves like verify-ca (skip hostname)
84
- const shouldVerifyHostname = sslmode === "verify-full";
85
- const ssl = {
86
- rejectUnauthorized: shouldVerifyCa,
87
- };
88
- // Add CA certificate if verifying
89
- if (shouldVerifyCa && caValue) {
90
- ssl.ca = caValue;
91
- }
92
- // For verify-ca and libpq compatibility mode: skip hostname verification
93
- // This matches PostgreSQL semantics where verify-ca only checks the CA chain
94
- if (shouldVerifyCa && !shouldVerifyHostname) {
95
- ssl.checkServerIdentity = () => undefined;
96
- }
97
- // Get client certificate (optional, for mutual TLS)
98
- const certEnvVar = connectionType === "source"
99
- ? "PGDELTA_SOURCE_SSLCERT"
100
- : "PGDELTA_TARGET_SSLCERT";
101
- const certValue = await getCertValue(sslcert, certEnvVar);
102
- if (certValue) {
103
- ssl.cert = certValue;
104
- }
105
- // Get client key (optional, for mutual TLS, required if cert is provided)
106
- const keyEnvVar = connectionType === "source"
107
- ? "PGDELTA_SOURCE_SSLKEY"
108
- : "PGDELTA_TARGET_SSLKEY";
109
- const keyValue = await getCertValue(sslkey, keyEnvVar);
110
- if (keyValue) {
111
- ssl.key = keyValue;
112
- }
113
- // Warn if cert is provided without key (or vice versa)
114
- if ((ssl.cert && !ssl.key) || (!ssl.cert && ssl.key)) {
115
- throw new Error("Both client certificate and key must be provided together for mutual TLS");
116
- }
117
- return { ssl, cleanedUrl };
118
- }
119
- // No sslmode specified or invalid value - explicitly disable SSL
120
- return { cleanedUrl, ssl: false };
121
- }
122
30
  export async function createPlan(source, target, options = {}) {
123
- let sourcePool;
124
- let targetPool;
125
- let shouldCloseSource = false;
126
- let shouldCloseTarget = false;
127
- // Suppress expected shutdown errors from idle pool connections (57P01 = admin_shutdown)
128
- const onError = (err) => {
129
- if (err.code !== "57P01") {
130
- console.error("Pool error:", err);
31
+ const resolvePool = async (input, label) => {
32
+ if (typeof input === "string") {
33
+ const managed = await createManagedPool(input, {
34
+ role: options.role,
35
+ label,
36
+ });
37
+ return { pool: managed.pool, shouldClose: true };
131
38
  }
39
+ return { pool: input, shouldClose: false };
132
40
  };
133
- if (typeof source === "string") {
134
- const sslConfig = await parseSslConfig(source, "source");
135
- sourcePool = createPool(sslConfig.cleanedUrl, {
136
- ...(sslConfig.ssl !== undefined ? { ssl: sslConfig.ssl } : {}),
137
- onError,
138
- onConnect: async (client) => {
139
- // Force fully qualified names in catalog queries
140
- await client.query("SET search_path = ''");
141
- if (options.role) {
142
- await client.query(`SET ROLE ${escapeIdentifier(options.role)}`);
143
- }
144
- },
145
- });
146
- shouldCloseSource = true;
147
- }
148
- else {
149
- sourcePool = source;
150
- }
151
- if (typeof target === "string") {
152
- const sslConfig = await parseSslConfig(target, "target");
153
- targetPool = createPool(sslConfig.cleanedUrl, {
154
- ...(sslConfig.ssl !== undefined ? { ssl: sslConfig.ssl } : {}),
155
- onError,
156
- onConnect: async (client) => {
157
- // Force fully qualified names in catalog queries
158
- await client.query("SET search_path = ''");
159
- if (options.role) {
160
- await client.query(`SET ROLE ${escapeIdentifier(options.role)}`);
161
- }
162
- },
163
- });
164
- shouldCloseTarget = true;
165
- }
166
- else {
167
- targetPool = target;
168
- }
169
- const sourceExtraction = extractCatalog(sourcePool);
170
- const targetExtraction = extractCatalog(targetPool);
41
+ /**
42
+ * Resolve a CatalogInput to a Catalog, tracking pools that need cleanup.
43
+ */
44
+ const resolveCatalog = async (input, label, pools) => {
45
+ if (input instanceof Catalog) {
46
+ return input;
47
+ }
48
+ const resolved = await resolvePool(input, label);
49
+ pools.push(resolved);
50
+ return extractCatalog(resolved.pool);
51
+ };
52
+ const pools = [];
171
53
  try {
172
- const [fromCatalog, toCatalog] = await Promise.all([
173
- sourceExtraction,
174
- targetExtraction,
175
- ]);
54
+ const toCatalog = await resolveCatalog(target, "target", pools);
55
+ const fromCatalog = source !== null
56
+ ? await resolveCatalog(source, "source", pools)
57
+ : await createEmptyCatalog(toCatalog.version, toCatalog.currentUser);
176
58
  return buildPlanForCatalogs(fromCatalog, toCatalog, options);
177
59
  }
178
- catch (error) {
179
- // When one extraction fails, the other may still have in-flight queries.
180
- // Wait for both to settle before pool cleanup to prevent unhandled
181
- // rejections from connections being terminated mid-flight.
182
- await Promise.allSettled([sourceExtraction, targetExtraction]);
183
- throw error;
184
- }
185
60
  finally {
186
- const closers = [];
187
- if (shouldCloseSource)
188
- closers.push(endPool(sourcePool));
189
- if (shouldCloseTarget)
190
- closers.push(endPool(targetPool));
191
- if (closers.length) {
61
+ const closers = pools
62
+ .filter((p) => p.shouldClose)
63
+ .map((p) => endPool(p.pool));
64
+ if (closers.length)
192
65
  await Promise.all(closers);
193
- }
194
66
  }
195
67
  }
196
68
  /**
@@ -199,6 +71,7 @@ export async function createPlan(source, target, options = {}) {
199
71
  function buildPlanForCatalogs(fromCatalog, toCatalog, options = {}) {
200
72
  const changes = diffCatalogs(fromCatalog, toCatalog, {
201
73
  role: options.role,
74
+ skipDefaultPrivilegeSubtraction: options.skipDefaultPrivilegeSubtraction,
202
75
  });
203
76
  const filterOption = options.filter;
204
77
  const serializeOption = options.serialize;
@@ -231,9 +104,18 @@ function buildPlanForCatalogs(fromCatalog, toCatalog, options = {}) {
231
104
  }
232
105
  // Use filter from final integration
233
106
  const filterFn = finalIntegration?.filter;
234
- const filteredChanges = filterFn
107
+ let filteredChanges = filterFn
235
108
  ? changes.filter((change) => filterFn(change))
236
109
  : changes;
110
+ // Cascade dependency exclusions: when a change is excluded by the filter,
111
+ // also exclude changes that depend on it (via requires or pg_depend).
112
+ // DSL filters: cascade only if explicitly opted in (cascade: true). Function filters: cascade by default.
113
+ const shouldCascade = isFilterDSL
114
+ ? filterDSL?.cascade === true
115
+ : true;
116
+ if (filterFn && filteredChanges.length < changes.length && shouldCascade) {
117
+ filteredChanges = cascadeExclusions(filteredChanges, changes, toCatalog.depends);
118
+ }
237
119
  if (filteredChanges.length === 0) {
238
120
  return null;
239
121
  }
@@ -242,6 +124,84 @@ function buildPlanForCatalogs(fromCatalog, toCatalog, options = {}) {
242
124
  return { plan, sortedChanges, ctx };
243
125
  }
244
126
  // ============================================================================
127
+ // Dependency Cascading
128
+ // ============================================================================
129
+ /**
130
+ * Cascade exclusions through dependency relationships.
131
+ *
132
+ * When a change is excluded by the filter, any change that depends on it
133
+ * (via explicit `requires` or via catalog `pg_depend`) should also be excluded.
134
+ * This runs as a fixpoint loop, bounded by the total number of changes to
135
+ * guarantee deterministic termination.
136
+ *
137
+ * @param filteredChanges - Changes that passed the initial filter
138
+ * @param allChanges - All changes before filtering
139
+ * @param catalogDepends - Dependency rows from the target catalog (pg_depend)
140
+ * @returns The filtered changes with cascading exclusions applied
141
+ */
142
+ function cascadeExclusions(filteredChanges, allChanges, catalogDepends) {
143
+ // Collect stableIds created by initially-excluded changes
144
+ const filteredSet = new Set(filteredChanges);
145
+ const excludedIds = new Set();
146
+ for (const change of allChanges) {
147
+ if (!filteredSet.has(change)) {
148
+ for (const id of change.creates ?? []) {
149
+ excludedIds.add(id);
150
+ }
151
+ }
152
+ }
153
+ if (excludedIds.size === 0) {
154
+ return filteredChanges;
155
+ }
156
+ // Build reverse dependency map: referenced_stable_id -> Set(dependent_stable_ids)
157
+ const catalogDependents = new Map();
158
+ for (const dep of catalogDepends) {
159
+ const existing = catalogDependents.get(dep.referenced_stable_id);
160
+ if (existing) {
161
+ existing.add(dep.dependent_stable_id);
162
+ }
163
+ else {
164
+ catalogDependents.set(dep.referenced_stable_id, new Set([dep.dependent_stable_id]));
165
+ }
166
+ }
167
+ // Fixpoint loop: bounded by total changes to guarantee termination.
168
+ // Each iteration must remove at least one change, otherwise we break.
169
+ let result = filteredChanges;
170
+ for (let i = 0; i < allChanges.length; i++) {
171
+ const beforeLength = result.length;
172
+ result = result.filter((change) => {
173
+ // Check explicit requirements: does this change require an excluded id?
174
+ const requires = change.requires ?? [];
175
+ if (requires.some((dep) => excludedIds.has(dep))) {
176
+ for (const id of change.creates ?? []) {
177
+ excludedIds.add(id);
178
+ }
179
+ return false;
180
+ }
181
+ // Check catalog dependencies: does anything this change creates
182
+ // depend on an excluded id via pg_depend?
183
+ const creates = change.creates ?? [];
184
+ for (const createdId of creates) {
185
+ for (const excludedId of excludedIds) {
186
+ const dependents = catalogDependents.get(excludedId);
187
+ if (dependents?.has(createdId)) {
188
+ for (const id of creates) {
189
+ excludedIds.add(id);
190
+ }
191
+ return false;
192
+ }
193
+ }
194
+ }
195
+ return true;
196
+ });
197
+ // No changes removed this iteration — fixpoint reached
198
+ if (result.length === beforeLength) {
199
+ break;
200
+ }
201
+ }
202
+ return result;
203
+ }
204
+ // ============================================================================
245
205
  // Plan Building
246
206
  // ============================================================================
247
207
  /**
@@ -139,10 +139,22 @@ export function getParentInfo(change) {
139
139
  const parentType = change.index.table_relkind === "m" ? "materialized_view" : "table";
140
140
  return { type: parentType, name: change.index.table_name };
141
141
  }
142
- case "trigger":
143
- return { type: "table", name: change.trigger.table_name };
144
- case "rule":
145
- return { type: "table", name: change.rule.table_name };
142
+ case "trigger": {
143
+ const parentType = change.trigger.table_relkind === "v"
144
+ ? "view"
145
+ : change.trigger.table_relkind === "m"
146
+ ? "materialized_view"
147
+ : "table";
148
+ return { type: parentType, name: change.trigger.table_name };
149
+ }
150
+ case "rule": {
151
+ const parentType = change.rule.relation_kind === "v"
152
+ ? "view"
153
+ : change.rule.relation_kind === "m"
154
+ ? "materialized_view"
155
+ : "table";
156
+ return { type: parentType, name: change.rule.table_name };
157
+ }
146
158
  case "rls_policy":
147
159
  return { type: "table", name: change.policy.table_name };
148
160
  case "aggregate":
@@ -84,7 +84,7 @@ import { CreateProcedure } from "../../objects/procedure/changes/procedure.creat
84
84
  import { DropProcedure } from "../../objects/procedure/changes/procedure.drop.js";
85
85
  import { GrantProcedurePrivileges, RevokeGrantOptionProcedurePrivileges, RevokeProcedurePrivileges, } from "../../objects/procedure/changes/procedure.privilege.js";
86
86
  import { Procedure } from "../../objects/procedure/procedure.model.js";
87
- import { AlterPublicationAddSchemas, AlterPublicationAddTables, AlterPublicationDropSchemas, AlterPublicationDropTables, AlterPublicationSetForAllTables, AlterPublicationSetList, AlterPublicationSetOptions, AlterPublicationSetOwner, } from "../../objects/publication/changes/publication.alter.js";
87
+ import { AlterPublicationAddSchemas, AlterPublicationAddTables, AlterPublicationDropSchemas, AlterPublicationDropTables, AlterPublicationSetList, AlterPublicationSetOptions, AlterPublicationSetOwner, } from "../../objects/publication/changes/publication.alter.js";
88
88
  import { CreateCommentOnPublication, DropCommentOnPublication, } from "../../objects/publication/changes/publication.comment.js";
89
89
  // ── Publication changes ─────────────────────────────────────────────────────
90
90
  import { CreatePublication } from "../../objects/publication/changes/publication.create.js";
@@ -769,6 +769,7 @@ const trigger = new Trigger({
769
769
  schema: "public",
770
770
  name: "trg_audit",
771
771
  table_name: "table_with_very_long_name_for_formatting_and_wrapping_test",
772
+ table_relkind: "r",
772
773
  function_schema: "public",
773
774
  function_name: "audit_trigger_fn",
774
775
  trigger_type: 7,
@@ -931,6 +932,7 @@ const role = new Role({
931
932
  objtype: "r",
932
933
  grantee: "app_reader",
933
934
  privileges: [{ privilege: "SELECT", grantable: false }],
935
+ is_implicit: false,
934
936
  },
935
937
  ],
936
938
  });
@@ -1572,10 +1574,6 @@ const changeCases = [
1572
1574
  setPublishViaPartitionRoot: true,
1573
1575
  }),
1574
1576
  },
1575
- {
1576
- label: "publication.alter.set_all_tables",
1577
- change: new AlterPublicationSetForAllTables({ publication }),
1578
- },
1579
1577
  {
1580
1578
  label: "publication.alter.set_list",
1581
1579
  change: new AlterPublicationSetList({ publication }),