@supabase/pg-delta 1.0.0-alpha.22 → 1.0.0-alpha.24

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 (228) hide show
  1. package/dist/core/catalog.model.js +1 -0
  2. package/dist/core/integrations/filter/flatten.js +13 -0
  3. package/dist/core/objects/aggregate/aggregate.diff.js +16 -0
  4. package/dist/core/objects/aggregate/aggregate.model.d.ts +10 -0
  5. package/dist/core/objects/aggregate/aggregate.model.js +19 -1
  6. package/dist/core/objects/aggregate/changes/aggregate.base.d.ts +1 -1
  7. package/dist/core/objects/aggregate/changes/aggregate.security-label.d.ts +28 -0
  8. package/dist/core/objects/aggregate/changes/aggregate.security-label.js +64 -0
  9. package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +2 -1
  10. package/dist/core/objects/base.model.d.ts +8 -0
  11. package/dist/core/objects/base.model.js +2 -0
  12. package/dist/core/objects/domain/changes/domain.base.d.ts +1 -1
  13. package/dist/core/objects/domain/changes/domain.security-label.d.ts +28 -0
  14. package/dist/core/objects/domain/changes/domain.security-label.js +61 -0
  15. package/dist/core/objects/domain/changes/domain.types.d.ts +2 -1
  16. package/dist/core/objects/domain/domain.diff.js +16 -0
  17. package/dist/core/objects/domain/domain.model.d.ts +10 -0
  18. package/dist/core/objects/domain/domain.model.js +19 -1
  19. package/dist/core/objects/event-trigger/changes/event-trigger.base.d.ts +1 -1
  20. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.d.ts +28 -0
  21. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.js +61 -0
  22. package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +2 -1
  23. package/dist/core/objects/event-trigger/event-trigger.diff.js +16 -0
  24. package/dist/core/objects/event-trigger/event-trigger.model.d.ts +10 -0
  25. package/dist/core/objects/event-trigger/event-trigger.model.js +19 -1
  26. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.d.ts +1 -1
  27. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.d.ts +28 -0
  28. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.js +61 -0
  29. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +2 -1
  30. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -0
  31. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +22 -0
  32. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +20 -1
  33. package/dist/core/objects/materialized-view/changes/materialized-view.base.d.ts +1 -1
  34. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.d.ts +28 -0
  35. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.js +61 -0
  36. package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +2 -1
  37. package/dist/core/objects/materialized-view/materialized-view.diff.js +18 -0
  38. package/dist/core/objects/materialized-view/materialized-view.model.d.ts +22 -0
  39. package/dist/core/objects/materialized-view/materialized-view.model.js +20 -1
  40. package/dist/core/objects/procedure/changes/procedure.base.d.ts +1 -1
  41. package/dist/core/objects/procedure/changes/procedure.security-label.d.ts +28 -0
  42. package/dist/core/objects/procedure/changes/procedure.security-label.js +69 -0
  43. package/dist/core/objects/procedure/changes/procedure.types.d.ts +2 -1
  44. package/dist/core/objects/procedure/procedure.diff.js +16 -0
  45. package/dist/core/objects/procedure/procedure.model.d.ts +10 -0
  46. package/dist/core/objects/procedure/procedure.model.js +19 -1
  47. package/dist/core/objects/publication/changes/publication.base.d.ts +1 -1
  48. package/dist/core/objects/publication/changes/publication.security-label.d.ts +28 -0
  49. package/dist/core/objects/publication/changes/publication.security-label.js +61 -0
  50. package/dist/core/objects/publication/changes/publication.types.d.ts +2 -1
  51. package/dist/core/objects/publication/publication.diff.js +16 -0
  52. package/dist/core/objects/publication/publication.model.d.ts +14 -0
  53. package/dist/core/objects/publication/publication.model.js +20 -1
  54. package/dist/core/objects/role/changes/role.base.d.ts +1 -1
  55. package/dist/core/objects/role/changes/role.security-label.d.ts +28 -0
  56. package/dist/core/objects/role/changes/role.security-label.js +61 -0
  57. package/dist/core/objects/role/changes/role.types.d.ts +2 -1
  58. package/dist/core/objects/role/role.diff.js +16 -0
  59. package/dist/core/objects/role/role.model.d.ts +10 -0
  60. package/dist/core/objects/role/role.model.js +29 -0
  61. package/dist/core/objects/schema/changes/schema.base.d.ts +1 -1
  62. package/dist/core/objects/schema/changes/schema.security-label.d.ts +28 -0
  63. package/dist/core/objects/schema/changes/schema.security-label.js +61 -0
  64. package/dist/core/objects/schema/changes/schema.types.d.ts +2 -1
  65. package/dist/core/objects/schema/schema.diff.js +24 -1
  66. package/dist/core/objects/schema/schema.model.d.ts +10 -0
  67. package/dist/core/objects/schema/schema.model.js +18 -1
  68. package/dist/core/objects/security-label.types.d.ts +20 -0
  69. package/dist/core/objects/security-label.types.js +46 -0
  70. package/dist/core/objects/sequence/changes/sequence.base.d.ts +1 -1
  71. package/dist/core/objects/sequence/changes/sequence.security-label.d.ts +28 -0
  72. package/dist/core/objects/sequence/changes/sequence.security-label.js +61 -0
  73. package/dist/core/objects/sequence/changes/sequence.types.d.ts +2 -1
  74. package/dist/core/objects/sequence/sequence.diff.js +16 -0
  75. package/dist/core/objects/sequence/sequence.model.d.ts +10 -0
  76. package/dist/core/objects/sequence/sequence.model.js +19 -1
  77. package/dist/core/objects/subscription/changes/subscription.base.d.ts +1 -1
  78. package/dist/core/objects/subscription/changes/subscription.security-label.d.ts +28 -0
  79. package/dist/core/objects/subscription/changes/subscription.security-label.js +61 -0
  80. package/dist/core/objects/subscription/changes/subscription.types.d.ts +2 -1
  81. package/dist/core/objects/subscription/subscription.diff.js +16 -0
  82. package/dist/core/objects/subscription/subscription.model.d.ts +10 -0
  83. package/dist/core/objects/subscription/subscription.model.js +19 -1
  84. package/dist/core/objects/table/changes/table.base.d.ts +1 -1
  85. package/dist/core/objects/table/changes/table.security-label.d.ts +63 -0
  86. package/dist/core/objects/table/changes/table.security-label.js +134 -0
  87. package/dist/core/objects/table/changes/table.types.d.ts +2 -1
  88. package/dist/core/objects/table/table.diff.js +49 -0
  89. package/dist/core/objects/table/table.model.d.ts +30 -0
  90. package/dist/core/objects/table/table.model.js +34 -2
  91. package/dist/core/objects/type/composite-type/changes/composite-type.base.d.ts +1 -1
  92. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.d.ts +28 -0
  93. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.js +61 -0
  94. package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +2 -1
  95. package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -0
  96. package/dist/core/objects/type/composite-type/composite-type.model.d.ts +22 -0
  97. package/dist/core/objects/type/composite-type/composite-type.model.js +22 -2
  98. package/dist/core/objects/type/enum/changes/enum.base.d.ts +1 -1
  99. package/dist/core/objects/type/enum/changes/enum.security-label.d.ts +28 -0
  100. package/dist/core/objects/type/enum/changes/enum.security-label.js +61 -0
  101. package/dist/core/objects/type/enum/changes/enum.types.d.ts +2 -1
  102. package/dist/core/objects/type/enum/enum.diff.js +16 -0
  103. package/dist/core/objects/type/enum/enum.model.d.ts +10 -0
  104. package/dist/core/objects/type/enum/enum.model.js +20 -1
  105. package/dist/core/objects/type/range/changes/range.base.d.ts +1 -1
  106. package/dist/core/objects/type/range/changes/range.security-label.d.ts +28 -0
  107. package/dist/core/objects/type/range/changes/range.security-label.js +61 -0
  108. package/dist/core/objects/type/range/changes/range.types.d.ts +2 -1
  109. package/dist/core/objects/type/range/range.diff.js +16 -0
  110. package/dist/core/objects/type/range/range.model.d.ts +10 -0
  111. package/dist/core/objects/type/range/range.model.js +19 -1
  112. package/dist/core/objects/utils.d.ts +1 -0
  113. package/dist/core/objects/utils.js +3 -0
  114. package/dist/core/objects/view/changes/view.base.d.ts +1 -1
  115. package/dist/core/objects/view/changes/view.security-label.d.ts +28 -0
  116. package/dist/core/objects/view/changes/view.security-label.js +61 -0
  117. package/dist/core/objects/view/changes/view.types.d.ts +2 -1
  118. package/dist/core/objects/view/view.diff.js +13 -0
  119. package/dist/core/objects/view/view.model.d.ts +26 -0
  120. package/dist/core/objects/view/view.model.js +20 -1
  121. package/dist/core/plan/sql-format/fixtures.js +1 -0
  122. package/dist/core/post-diff-normalization.d.ts +7 -0
  123. package/dist/core/post-diff-normalization.js +33 -4
  124. package/dist/core/sort/cycle-breakers.js +139 -17
  125. package/package.json +1 -1
  126. package/src/core/catalog.model.ts +1 -0
  127. package/src/core/integrations/filter/dsl.test.ts +27 -0
  128. package/src/core/integrations/filter/flatten.ts +16 -0
  129. package/src/core/objects/aggregate/aggregate.diff.ts +33 -0
  130. package/src/core/objects/aggregate/aggregate.model.ts +22 -1
  131. package/src/core/objects/aggregate/changes/aggregate.base.ts +5 -1
  132. package/src/core/objects/aggregate/changes/aggregate.security-label.ts +99 -0
  133. package/src/core/objects/aggregate/changes/aggregate.types.ts +3 -1
  134. package/src/core/objects/base.model.ts +2 -0
  135. package/src/core/objects/domain/changes/domain.base.ts +5 -1
  136. package/src/core/objects/domain/changes/domain.security-label.test.ts +56 -0
  137. package/src/core/objects/domain/changes/domain.security-label.ts +77 -0
  138. package/src/core/objects/domain/changes/domain.types.ts +3 -1
  139. package/src/core/objects/domain/domain.diff.ts +33 -0
  140. package/src/core/objects/domain/domain.model.ts +22 -1
  141. package/src/core/objects/event-trigger/changes/event-trigger.base.ts +1 -1
  142. package/src/core/objects/event-trigger/changes/event-trigger.security-label.ts +95 -0
  143. package/src/core/objects/event-trigger/changes/event-trigger.types.ts +3 -1
  144. package/src/core/objects/event-trigger/event-trigger.diff.ts +33 -0
  145. package/src/core/objects/event-trigger/event-trigger.model.ts +22 -1
  146. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.ts +5 -1
  147. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.ts +95 -0
  148. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +3 -1
  149. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +33 -0
  150. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +24 -1
  151. package/src/core/objects/materialized-view/changes/materialized-view.base.ts +5 -1
  152. package/src/core/objects/materialized-view/changes/materialized-view.security-label.test.ts +63 -0
  153. package/src/core/objects/materialized-view/changes/materialized-view.security-label.ts +95 -0
  154. package/src/core/objects/materialized-view/changes/materialized-view.types.ts +3 -1
  155. package/src/core/objects/materialized-view/materialized-view.diff.ts +37 -0
  156. package/src/core/objects/materialized-view/materialized-view.model.ts +25 -4
  157. package/src/core/objects/procedure/changes/procedure.base.ts +5 -1
  158. package/src/core/objects/procedure/changes/procedure.security-label.ts +105 -0
  159. package/src/core/objects/procedure/changes/procedure.types.ts +3 -1
  160. package/src/core/objects/procedure/procedure.diff.ts +33 -0
  161. package/src/core/objects/procedure/procedure.model.ts +23 -2
  162. package/src/core/objects/publication/changes/publication.base.ts +1 -1
  163. package/src/core/objects/publication/changes/publication.security-label.ts +95 -0
  164. package/src/core/objects/publication/changes/publication.types.ts +3 -1
  165. package/src/core/objects/publication/publication.diff.ts +33 -0
  166. package/src/core/objects/publication/publication.model.ts +24 -1
  167. package/src/core/objects/role/changes/role.base.ts +2 -1
  168. package/src/core/objects/role/changes/role.security-label.ts +77 -0
  169. package/src/core/objects/role/changes/role.types.ts +3 -1
  170. package/src/core/objects/role/role.diff.ts +33 -0
  171. package/src/core/objects/role/role.model.ts +32 -0
  172. package/src/core/objects/schema/changes/schema.alter.test.ts +1 -0
  173. package/src/core/objects/schema/changes/schema.base.ts +5 -1
  174. package/src/core/objects/schema/changes/schema.create.test.ts +1 -0
  175. package/src/core/objects/schema/changes/schema.drop.test.ts +1 -0
  176. package/src/core/objects/schema/changes/schema.security-label.test.ts +76 -0
  177. package/src/core/objects/schema/changes/schema.security-label.ts +77 -0
  178. package/src/core/objects/schema/changes/schema.types.ts +3 -1
  179. package/src/core/objects/schema/schema.diff.test.ts +1 -0
  180. package/src/core/objects/schema/schema.diff.ts +43 -1
  181. package/src/core/objects/schema/schema.model.ts +21 -1
  182. package/src/core/objects/security-label.types.test.ts +106 -0
  183. package/src/core/objects/security-label.types.ts +61 -0
  184. package/src/core/objects/sequence/changes/sequence.base.ts +5 -1
  185. package/src/core/objects/sequence/changes/sequence.security-label.test.ts +58 -0
  186. package/src/core/objects/sequence/changes/sequence.security-label.ts +92 -0
  187. package/src/core/objects/sequence/changes/sequence.types.ts +3 -1
  188. package/src/core/objects/sequence/sequence.diff.ts +33 -0
  189. package/src/core/objects/sequence/sequence.model.ts +22 -1
  190. package/src/core/objects/subscription/changes/subscription.base.ts +1 -1
  191. package/src/core/objects/subscription/changes/subscription.security-label.ts +95 -0
  192. package/src/core/objects/subscription/changes/subscription.types.ts +3 -1
  193. package/src/core/objects/subscription/subscription.diff.ts +33 -0
  194. package/src/core/objects/subscription/subscription.model.ts +22 -1
  195. package/src/core/objects/table/changes/table.base.ts +5 -1
  196. package/src/core/objects/table/changes/table.security-label.test.ts +140 -0
  197. package/src/core/objects/table/changes/table.security-label.ts +183 -0
  198. package/src/core/objects/table/changes/table.types.ts +3 -1
  199. package/src/core/objects/table/table.diff.ts +87 -0
  200. package/src/core/objects/table/table.model.ts +42 -2
  201. package/src/core/objects/type/composite-type/changes/composite-type.base.ts +5 -1
  202. package/src/core/objects/type/composite-type/changes/composite-type.security-label.ts +95 -0
  203. package/src/core/objects/type/composite-type/changes/composite-type.types.ts +3 -1
  204. package/src/core/objects/type/composite-type/composite-type.diff.ts +33 -0
  205. package/src/core/objects/type/composite-type/composite-type.model.ts +26 -2
  206. package/src/core/objects/type/enum/changes/enum.base.ts +5 -1
  207. package/src/core/objects/type/enum/changes/enum.security-label.ts +77 -0
  208. package/src/core/objects/type/enum/changes/enum.types.ts +3 -1
  209. package/src/core/objects/type/enum/enum.diff.ts +33 -0
  210. package/src/core/objects/type/enum/enum.model.ts +25 -1
  211. package/src/core/objects/type/range/changes/range.base.ts +5 -1
  212. package/src/core/objects/type/range/changes/range.security-label.ts +77 -0
  213. package/src/core/objects/type/range/changes/range.types.ts +3 -1
  214. package/src/core/objects/type/range/range.diff.ts +33 -0
  215. package/src/core/objects/type/range/range.model.ts +22 -1
  216. package/src/core/objects/utils.ts +3 -0
  217. package/src/core/objects/view/changes/view.base.ts +5 -1
  218. package/src/core/objects/view/changes/view.security-label.test.ts +64 -0
  219. package/src/core/objects/view/changes/view.security-label.ts +77 -0
  220. package/src/core/objects/view/changes/view.types.ts +3 -1
  221. package/src/core/objects/view/view.diff.ts +31 -0
  222. package/src/core/objects/view/view.model.ts +25 -2
  223. package/src/core/plan/sql-format/fixtures.ts +1 -0
  224. package/src/core/post-diff-normalization.test.ts +123 -0
  225. package/src/core/post-diff-normalization.ts +40 -4
  226. package/src/core/sort/cycle-breakers.test.ts +236 -2
  227. package/src/core/sort/cycle-breakers.ts +184 -24
  228. package/src/core/sort/sort-changes.test.ts +317 -0
@@ -3,6 +3,12 @@ import type { Change } from "./change.types.ts";
3
3
  import { CreateIndex } from "./objects/index/changes/index.create.ts";
4
4
  import { DropIndex } from "./objects/index/changes/index.drop.ts";
5
5
  import { Index, type IndexProps } from "./objects/index/index.model.ts";
6
+ import { CreateSequence } from "./objects/sequence/changes/sequence.create.ts";
7
+ import { DropSequence } from "./objects/sequence/changes/sequence.drop.ts";
8
+ import {
9
+ Sequence,
10
+ type SequenceProps,
11
+ } from "./objects/sequence/sequence.model.ts";
6
12
  import {
7
13
  AlterTableAddConstraint,
8
14
  AlterTableChangeOwner,
@@ -304,6 +310,123 @@ describe("normalizePostDiffChanges", () => {
304
310
  ).toHaveLength(1);
305
311
  });
306
312
 
313
+ describe("DropSequence pruning on replaced tables", () => {
314
+ const baseSequenceProps: SequenceProps = {
315
+ schema: "public",
316
+ name: "project_link_type_id_seq",
317
+ data_type: "integer",
318
+ start_value: 1,
319
+ minimum_value: 1n,
320
+ maximum_value: 2147483647n,
321
+ increment: 1,
322
+ cycle_option: false,
323
+ cache_size: 1,
324
+ persistence: "p",
325
+ owned_by_schema: "public",
326
+ owned_by_table: "project_link_type",
327
+ owned_by_column: "id",
328
+ comment: null,
329
+ privileges: [],
330
+ owner: "postgres",
331
+ };
332
+
333
+ test("prunes DropSequence when its OWNED BY table is in replacedTableIds", () => {
334
+ const replacedTable = new Table({
335
+ ...baseTableProps,
336
+ name: "project_link_type",
337
+ columns: [{ ...integerColumn("id", 1), not_null: true }],
338
+ });
339
+ const ownedSequence = new Sequence(baseSequenceProps);
340
+
341
+ const dropSequence = new DropSequence({ sequence: ownedSequence });
342
+ const dropTable = new DropTable({ table: replacedTable });
343
+ const createTable = new CreateTable({ table: replacedTable });
344
+
345
+ const changes: Change[] = [dropSequence, dropTable, createTable];
346
+
347
+ const normalized = normalizePostDiffChanges({
348
+ changes,
349
+ replacedTableIds: new Set([replacedTable.stableId]),
350
+ });
351
+
352
+ expect(normalized.some((change) => change instanceof DropSequence)).toBe(
353
+ false,
354
+ );
355
+ expect(normalized).toContain(dropTable);
356
+ expect(normalized).toContain(createTable);
357
+ });
358
+
359
+ test("keeps DropSequence whose OWNED BY table is not in replacedTableIds", () => {
360
+ const survivingTable = new Table({
361
+ ...baseTableProps,
362
+ name: "project_link_type",
363
+ columns: [{ ...integerColumn("id", 1), not_null: true }],
364
+ });
365
+ const ownedSequence = new Sequence(baseSequenceProps);
366
+
367
+ const dropSequence = new DropSequence({ sequence: ownedSequence });
368
+
369
+ const normalized = normalizePostDiffChanges({
370
+ changes: [dropSequence],
371
+ // Different table is being replaced; the sequence's OWNED BY does
372
+ // not match, so DropSequence must survive.
373
+ replacedTableIds: new Set([
374
+ `table:${survivingTable.schema}.unrelated_table` as const,
375
+ ]),
376
+ });
377
+
378
+ expect(normalized).toContain(dropSequence);
379
+ });
380
+
381
+ test("keeps DropSequence with no OWNED BY when replacedTableIds is non-empty", () => {
382
+ const orphanSequence = new Sequence({
383
+ ...baseSequenceProps,
384
+ owned_by_schema: null,
385
+ owned_by_table: null,
386
+ owned_by_column: null,
387
+ });
388
+
389
+ const dropSequence = new DropSequence({ sequence: orphanSequence });
390
+
391
+ const normalized = normalizePostDiffChanges({
392
+ changes: [dropSequence],
393
+ replacedTableIds: new Set(["table:public.project_link_type" as const]),
394
+ });
395
+
396
+ expect(normalized).toContain(dropSequence);
397
+ });
398
+
399
+ test("keeps unrelated CreateSequence and DropSequence even when its non-owning table is replaced", () => {
400
+ const sequenceA = new Sequence(baseSequenceProps);
401
+ const sequenceB = new Sequence({
402
+ ...baseSequenceProps,
403
+ name: "unrelated_seq",
404
+ owned_by_schema: null,
405
+ owned_by_table: null,
406
+ owned_by_column: null,
407
+ });
408
+
409
+ const dropOwned = new DropSequence({ sequence: sequenceA });
410
+ const createUnrelated = new CreateSequence({ sequence: sequenceB });
411
+
412
+ const replacedTable = new Table({
413
+ ...baseTableProps,
414
+ name: "project_link_type",
415
+ columns: [{ ...integerColumn("id", 1), not_null: true }],
416
+ });
417
+
418
+ const normalized = normalizePostDiffChanges({
419
+ changes: [dropOwned, createUnrelated],
420
+ replacedTableIds: new Set([replacedTable.stableId]),
421
+ });
422
+
423
+ expect(normalized.some((change) => change instanceof DropSequence)).toBe(
424
+ false,
425
+ );
426
+ expect(normalized).toContain(createUnrelated);
427
+ });
428
+ });
429
+
307
430
  describe("restoreReplicaIdentityAfterIndexReplace", () => {
308
431
  const baseIndexProps: IndexProps = {
309
432
  schema: "public",
@@ -1,6 +1,7 @@
1
1
  import type { Change } from "./change.types.ts";
2
2
  import { CreateIndex } from "./objects/index/changes/index.create.ts";
3
3
  import { DropIndex } from "./objects/index/changes/index.drop.ts";
4
+ import { DropSequence } from "./objects/sequence/changes/sequence.drop.ts";
4
5
  import {
5
6
  AlterTableAddConstraint,
6
7
  AlterTableDropColumn,
@@ -24,12 +25,40 @@ function isSupersededByTableReplacement(
24
25
  replacedTableIds: ReadonlySet<string>,
25
26
  ): boolean {
26
27
  if (
27
- !(change instanceof AlterTableDropColumn) &&
28
- !(change instanceof AlterTableDropConstraint)
28
+ change instanceof AlterTableDropColumn ||
29
+ change instanceof AlterTableDropConstraint
29
30
  ) {
30
- return false;
31
+ return replacedTableIds.has(change.table.stableId);
31
32
  }
32
- return replacedTableIds.has(change.table.stableId);
33
+
34
+ // `DropSequence(S)` is superseded when S is OWNED BY a column on a table
35
+ // that `expandReplaceDependencies` has promoted to `DropTable + CreateTable`
36
+ // in the same plan. PostgreSQL cascade-drops the OWNED BY sequence as part
37
+ // of the DROP TABLE, so the explicit DROP SEQUENCE is redundant and — more
38
+ // importantly — closes an unbreakable `DropSequence ↔ DropTable` cycle in
39
+ // the drop phase via the bidirectional pg_depend edges between the
40
+ // sequence and its owning column (`column → sequence` for the DEFAULT
41
+ // nextval reference, `sequence → column` for the OWNED BY auto-dependency).
42
+ // The alpha.15 short-circuit in `diffSequences.dropped` only suppresses
43
+ // `DropSequence` when the owning table itself is gone from `branchTables`;
44
+ // here the table survives in branch and the replacement is added later by
45
+ // the expander, so this whole-plan rewrite has to happen post-diff.
46
+ if (change instanceof DropSequence) {
47
+ if (
48
+ !change.sequence.owned_by_schema ||
49
+ !change.sequence.owned_by_table ||
50
+ !change.sequence.owned_by_column
51
+ ) {
52
+ return false;
53
+ }
54
+ const ownedByTableId = stableId.table(
55
+ change.sequence.owned_by_schema,
56
+ change.sequence.owned_by_table,
57
+ );
58
+ return replacedTableIds.has(ownedByTableId);
59
+ }
60
+
61
+ return false;
33
62
  }
34
63
 
35
64
  /**
@@ -219,6 +248,13 @@ function restoreReplicaIdentityAfterIndexReplace(
219
248
  * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
220
249
  * would try to drop a column that no longer exists in the freshly
221
250
  * recreated table.
251
+ * - Prunes `DropSequence(S)` changes when `S` is `OWNED BY` a column on a
252
+ * table promoted to `DropTable + CreateTable` by the expander. The
253
+ * `DROP TABLE` cascade drops the sequence at apply time; emitting an
254
+ * explicit `DROP SEQUENCE` in the same drop phase both duplicates the
255
+ * cascade and forms an unbreakable `DropSequence ↔ DropTable` cycle on
256
+ * the bidirectional pg_depend edges between the sequence and the
257
+ * owning column.
222
258
  * - Dedupes duplicate `AlterTableAddConstraint` /
223
259
  * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
224
260
  * produced when `diffTables()` and `expandReplaceDependencies()` both
@@ -62,7 +62,9 @@ function fkConstraint(props: {
62
62
  fkColumn: string;
63
63
  targetSchema: string;
64
64
  targetTable: string;
65
+ targetColumn?: string;
65
66
  }) {
67
+ const targetColumn = props.targetColumn ?? "id";
66
68
  return {
67
69
  name: props.name,
68
70
  constraint_type: "f" as const,
@@ -78,7 +80,7 @@ function fkConstraint(props: {
78
80
  parent_table_schema: null,
79
81
  parent_table_name: null,
80
82
  key_columns: [props.fkColumn],
81
- foreign_key_columns: ["id"],
83
+ foreign_key_columns: [targetColumn],
82
84
  foreign_key_table: props.targetTable,
83
85
  foreign_key_schema: props.targetSchema,
84
86
  foreign_key_table_is_partition: false,
@@ -91,7 +93,41 @@ function fkConstraint(props: {
91
93
  match_type: "s" as const,
92
94
  check_expression: null,
93
95
  owner: "postgres",
94
- definition: `FOREIGN KEY (${props.fkColumn}) REFERENCES ${props.targetSchema}.${props.targetTable}(id)`,
96
+ definition: `FOREIGN KEY (${props.fkColumn}) REFERENCES ${props.targetSchema}.${props.targetTable}(${targetColumn})`,
97
+ comment: null,
98
+ };
99
+ }
100
+
101
+ function uniqueConstraint(name: string, column: string) {
102
+ return {
103
+ name,
104
+ constraint_type: "u" as const,
105
+ deferrable: false,
106
+ initially_deferred: false,
107
+ validated: true,
108
+ is_local: true,
109
+ no_inherit: false,
110
+ is_temporal: false,
111
+ is_partition_clone: false,
112
+ parent_constraint_schema: null,
113
+ parent_constraint_name: null,
114
+ parent_table_schema: null,
115
+ parent_table_name: null,
116
+ key_columns: [column],
117
+ foreign_key_columns: null,
118
+ foreign_key_table: null,
119
+ foreign_key_schema: null,
120
+ foreign_key_table_is_partition: null,
121
+ foreign_key_parent_schema: null,
122
+ foreign_key_parent_table: null,
123
+ foreign_key_effective_schema: null,
124
+ foreign_key_effective_table: null,
125
+ on_update: null,
126
+ on_delete: null,
127
+ match_type: null,
128
+ check_expression: null,
129
+ owner: "postgres",
130
+ definition: `UNIQUE (${column})`,
95
131
  comment: null,
96
132
  };
97
133
  }
@@ -448,6 +484,204 @@ describe("tryBreakCycleByChangeInjection", () => {
448
484
  expect(broken).toBeNull();
449
485
  });
450
486
 
487
+ test("publication FK-chain constraint-drop 3-cycle: injects terminal FK drop", () => {
488
+ // Schema:
489
+ // publication p includes labs and posts
490
+ // posts.lab_id REFERENCES labs(id)
491
+ // Diff drops posts and drops labs.unique_lab_id while also removing both
492
+ // tables from the publication. The FK edge from posts to the terminal
493
+ // constraint drop forms:
494
+ // AlterPublicationDropTables → DropTable(posts)
495
+ // DropTable(posts) → AlterTableDropConstraint(labs.unique_lab_id)
496
+ // AlterTableDropConstraint(labs.unique_lab_id) → AlterPublicationDropTables
497
+ const tableLabs = new Table({
498
+ ...baseTableProps,
499
+ name: "labs",
500
+ columns: [{ ...integerColumn("id", 1), not_null: true }],
501
+ constraints: [uniqueConstraint("unique_lab_id", "id")],
502
+ });
503
+ const tablePosts = new Table({
504
+ ...baseTableProps,
505
+ name: "posts",
506
+ columns: [
507
+ { ...integerColumn("id", 1), not_null: true },
508
+ integerColumn("lab_id", 2),
509
+ ],
510
+ constraints: [
511
+ fkConstraint({
512
+ name: "posts_lab_id_fkey",
513
+ fkColumn: "lab_id",
514
+ targetSchema: "public",
515
+ targetTable: "labs",
516
+ }),
517
+ ],
518
+ });
519
+ const publication = new Publication({
520
+ name: "p",
521
+ owner: "postgres",
522
+ comment: null,
523
+ all_tables: false,
524
+ publish_insert: true,
525
+ publish_update: true,
526
+ publish_delete: true,
527
+ publish_truncate: true,
528
+ publish_via_partition_root: false,
529
+ tables: [
530
+ { schema: "public", name: "labs", columns: null, row_filter: null },
531
+ { schema: "public", name: "posts", columns: null, row_filter: null },
532
+ ],
533
+ schemas: [],
534
+ });
535
+
536
+ const terminalDrop = new AlterTableDropConstraint({
537
+ table: tableLabs,
538
+ constraint: tableLabs.constraints[0],
539
+ });
540
+ const changes: Change[] = [
541
+ new AlterPublicationDropTables({
542
+ publication,
543
+ tables: publication.tables,
544
+ }),
545
+ new DropTable({ table: tablePosts }),
546
+ terminalDrop,
547
+ ];
548
+
549
+ const broken = tryBreakCycleByChangeInjection([0, 1, 2], changes);
550
+ if (broken === null) throw new Error("expected breaker to fire");
551
+
552
+ const injectedDrops = broken.filter(
553
+ (change): change is AlterTableDropConstraint =>
554
+ change instanceof AlterTableDropConstraint &&
555
+ change.table.stableId === tablePosts.stableId,
556
+ );
557
+ expect(injectedDrops).toHaveLength(1);
558
+ expect(injectedDrops[0].constraint.name).toBe("posts_lab_id_fkey");
559
+
560
+ const rewrittenPostsDrop = broken.find(
561
+ (change): change is DropTable =>
562
+ change instanceof DropTable &&
563
+ change.table.stableId === tablePosts.stableId,
564
+ );
565
+ if (!rewrittenPostsDrop) throw new Error("missing rewritten DropTable");
566
+ expect(
567
+ rewrittenPostsDrop.externallyDroppedConstraints.has("posts_lab_id_fkey"),
568
+ ).toBe(true);
569
+ expect(broken).toContain(terminalDrop);
570
+ });
571
+
572
+ test("publication FK-chain constraint-drop 4-cycle: injects FK drops along the dropped-table chain", () => {
573
+ // Schema:
574
+ // publication p includes labs, posts, and post_attachments
575
+ // post_attachments.post_id REFERENCES posts(id)
576
+ // posts.lab_id REFERENCES labs(id)
577
+ // Diff drops post_attachments and posts, drops labs.unique_lab_id,
578
+ // and removes all three tables from the publication.
579
+ const tableLabs = new Table({
580
+ ...baseTableProps,
581
+ name: "labs",
582
+ columns: [{ ...integerColumn("id", 1), not_null: true }],
583
+ constraints: [uniqueConstraint("unique_lab_id", "id")],
584
+ });
585
+ const tablePosts = new Table({
586
+ ...baseTableProps,
587
+ name: "posts",
588
+ columns: [
589
+ { ...integerColumn("id", 1), not_null: true },
590
+ integerColumn("lab_id", 2),
591
+ ],
592
+ constraints: [
593
+ fkConstraint({
594
+ name: "posts_lab_id_fkey",
595
+ fkColumn: "lab_id",
596
+ targetSchema: "public",
597
+ targetTable: "labs",
598
+ }),
599
+ ],
600
+ });
601
+ const tablePostAttachments = new Table({
602
+ ...baseTableProps,
603
+ name: "post_attachments",
604
+ columns: [
605
+ { ...integerColumn("id", 1), not_null: true },
606
+ integerColumn("post_id", 2),
607
+ ],
608
+ constraints: [
609
+ fkConstraint({
610
+ name: "post_attachments_post_id_fkey",
611
+ fkColumn: "post_id",
612
+ targetSchema: "public",
613
+ targetTable: "posts",
614
+ }),
615
+ ],
616
+ });
617
+ const publication = new Publication({
618
+ name: "p",
619
+ owner: "postgres",
620
+ comment: null,
621
+ all_tables: false,
622
+ publish_insert: true,
623
+ publish_update: true,
624
+ publish_delete: true,
625
+ publish_truncate: true,
626
+ publish_via_partition_root: false,
627
+ tables: [
628
+ { schema: "public", name: "labs", columns: null, row_filter: null },
629
+ {
630
+ schema: "public",
631
+ name: "post_attachments",
632
+ columns: null,
633
+ row_filter: null,
634
+ },
635
+ { schema: "public", name: "posts", columns: null, row_filter: null },
636
+ ],
637
+ schemas: [],
638
+ });
639
+
640
+ const terminalDrop = new AlterTableDropConstraint({
641
+ table: tableLabs,
642
+ constraint: tableLabs.constraints[0],
643
+ });
644
+ const changes: Change[] = [
645
+ new AlterPublicationDropTables({
646
+ publication,
647
+ tables: publication.tables,
648
+ }),
649
+ new DropTable({ table: tablePostAttachments }),
650
+ new DropTable({ table: tablePosts }),
651
+ terminalDrop,
652
+ ];
653
+
654
+ const broken = tryBreakCycleByChangeInjection([0, 1, 2, 3], changes);
655
+ if (broken === null) throw new Error("expected breaker to fire");
656
+
657
+ const injectedDropNames = broken
658
+ .filter(
659
+ (change): change is AlterTableDropConstraint =>
660
+ change instanceof AlterTableDropConstraint && change !== terminalDrop,
661
+ )
662
+ .map((change) => change.constraint.name)
663
+ .sort();
664
+ expect(injectedDropNames).toEqual([
665
+ "post_attachments_post_id_fkey",
666
+ "posts_lab_id_fkey",
667
+ ]);
668
+
669
+ for (const [tableId, constraintName] of [
670
+ [tablePostAttachments.stableId, "post_attachments_post_id_fkey"],
671
+ [tablePosts.stableId, "posts_lab_id_fkey"],
672
+ ] as const) {
673
+ const rewrittenDrop = broken.find(
674
+ (change): change is DropTable =>
675
+ change instanceof DropTable && change.table.stableId === tableId,
676
+ );
677
+ if (!rewrittenDrop) throw new Error(`missing DropTable for ${tableId}`);
678
+ expect(
679
+ rewrittenDrop.externallyDroppedConstraints.has(constraintName),
680
+ ).toBe(true);
681
+ }
682
+ expect(broken).toContain(terminalDrop);
683
+ });
684
+
451
685
  test("returns null for a cycle with no recognised pattern (e.g. publication-only)", () => {
452
686
  // Cycle of `AlterPublicationSetOwner` changes — neither FK nor
453
687
  // publication-column shape. Breaker must bail so the formatted