@supabase/pg-delta 1.0.0-alpha.23 → 1.0.0-alpha.25

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 (66) hide show
  1. package/dist/core/catalog.model.d.ts +2 -2
  2. package/dist/core/catalog.model.js +26 -21
  3. package/dist/core/integrations/supabase.js +84 -0
  4. package/dist/core/objects/aggregate/changes/aggregate.privilege.js +21 -9
  5. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.js +4 -1
  6. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.js +6 -3
  7. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.d.ts +11 -0
  8. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.js +11 -0
  9. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.js +4 -1
  10. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.js +6 -3
  11. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +11 -0
  12. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +11 -0
  13. package/dist/core/objects/foreign-data-wrapper/sensitive-options.d.ts +32 -0
  14. package/dist/core/objects/foreign-data-wrapper/sensitive-options.js +129 -0
  15. package/dist/core/objects/foreign-data-wrapper/server/changes/server.alter.js +4 -1
  16. package/dist/core/objects/foreign-data-wrapper/server/changes/server.create.js +6 -3
  17. package/dist/core/objects/foreign-data-wrapper/server/server.model.d.ts +10 -0
  18. package/dist/core/objects/foreign-data-wrapper/server/server.model.js +10 -0
  19. package/dist/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.js +4 -1
  20. package/dist/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.js +6 -3
  21. package/dist/core/objects/foreign-data-wrapper/user-mapping/user-mapping.model.d.ts +10 -0
  22. package/dist/core/objects/foreign-data-wrapper/user-mapping/user-mapping.model.js +10 -0
  23. package/dist/core/objects/rls-policy/rls-policy.model.d.ts +2 -2
  24. package/dist/core/objects/table/table.model.js +7 -2
  25. package/dist/core/post-diff-normalization.d.ts +7 -0
  26. package/dist/core/post-diff-normalization.js +33 -4
  27. package/dist/core/sort/cycle-breakers.js +139 -17
  28. package/package.json +1 -1
  29. package/src/core/catalog.model.ts +36 -20
  30. package/src/core/integrations/supabase.test.ts +198 -0
  31. package/src/core/integrations/supabase.ts +84 -0
  32. package/src/core/objects/aggregate/changes/aggregate.privilege.test.ts +79 -0
  33. package/src/core/objects/aggregate/changes/aggregate.privilege.ts +22 -9
  34. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.test.ts +34 -4
  35. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.ts +5 -1
  36. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.test.ts +34 -0
  37. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.ts +7 -5
  38. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts +11 -0
  39. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.test.ts +25 -4
  40. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.ts +5 -1
  41. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.test.ts +54 -0
  42. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.ts +7 -5
  43. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +11 -0
  44. package/src/core/objects/foreign-data-wrapper/sensitive-options.test.ts +98 -0
  45. package/src/core/objects/foreign-data-wrapper/sensitive-options.ts +133 -0
  46. package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.test.ts +39 -4
  47. package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.ts +5 -1
  48. package/src/core/objects/foreign-data-wrapper/server/changes/server.create.test.ts +36 -0
  49. package/src/core/objects/foreign-data-wrapper/server/changes/server.create.ts +7 -5
  50. package/src/core/objects/foreign-data-wrapper/server/server.model.ts +10 -0
  51. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts +39 -6
  52. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.ts +5 -1
  53. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts +38 -2
  54. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.ts +7 -5
  55. package/src/core/objects/foreign-data-wrapper/user-mapping/user-mapping.model.ts +10 -0
  56. package/src/core/objects/table/table.model.ts +7 -2
  57. package/src/core/plan/sql-format/format-off.test.ts +4 -4
  58. package/src/core/plan/sql-format/format-pretty-lower-leading.test.ts +4 -4
  59. package/src/core/plan/sql-format/format-pretty-narrow.test.ts +5 -4
  60. package/src/core/plan/sql-format/format-pretty-preserve.test.ts +4 -4
  61. package/src/core/plan/sql-format/format-pretty-upper.test.ts +4 -4
  62. package/src/core/post-diff-normalization.test.ts +123 -0
  63. package/src/core/post-diff-normalization.ts +40 -4
  64. package/src/core/sort/cycle-breakers.test.ts +236 -2
  65. package/src/core/sort/cycle-breakers.ts +184 -24
  66. package/src/core/sort/sort-changes.test.ts +317 -0
@@ -1,6 +1,7 @@
1
1
  import type { SerializeOptions } from "../../../../integrations/serialize/serialize.types.ts";
2
2
  import { quoteLiteral } from "../../../base.change.ts";
3
3
  import { stableId } from "../../../utils.ts";
4
+ import { redactOptionValue } from "../../sensitive-options.ts";
4
5
  import type { UserMapping } from "../user-mapping.model.ts";
5
6
  import { CreateUserMappingChange } from "./user-mapping.base.ts";
6
7
 
@@ -51,11 +52,12 @@ export class CreateUserMapping extends CreateUserMappingChange {
51
52
  if (this.userMapping.options && this.userMapping.options.length > 0) {
52
53
  const optionPairs: string[] = [];
53
54
  for (let i = 0; i < this.userMapping.options.length; i += 2) {
54
- if (i + 1 < this.userMapping.options.length) {
55
- optionPairs.push(
56
- `${this.userMapping.options[i]} ${quoteLiteral(this.userMapping.options[i + 1])}`,
57
- );
58
- }
55
+ const key = this.userMapping.options[i];
56
+ const value = this.userMapping.options[i + 1];
57
+ if (key === undefined || value === undefined) continue;
58
+ optionPairs.push(
59
+ `${key} ${quoteLiteral(redactOptionValue(key, value))}`,
60
+ );
59
61
  }
60
62
  if (optionPairs.length > 0) {
61
63
  parts.push(`OPTIONS (${optionPairs.join(", ")})`);
@@ -56,6 +56,16 @@ export class UserMapping extends BasePgModel {
56
56
  }
57
57
  }
58
58
 
59
+ /**
60
+ * Extract `pg_user_mapping` rows into `UserMapping` models.
61
+ *
62
+ * The returned models carry option values **verbatim** from
63
+ * `pg_user_mapping.umoptions`, which means cleartext secrets like
64
+ * `password` are present in memory. Always route through
65
+ * `extractCatalog` (which calls `normalizeCatalog`) before emitting
66
+ * options to any output channel — see CLI-1467 and
67
+ * `packages/pg-delta/src/core/objects/foreign-data-wrapper/sensitive-options.ts`.
68
+ */
59
69
  export async function extractUserMappings(pool: Pool): Promise<UserMapping[]> {
60
70
  const { rows: mappingRows } = await pool.query<UserMappingProps>(sql`
61
71
  select
@@ -341,8 +341,13 @@ select
341
341
  'no_inherit', c.connoinherit,
342
342
  'is_temporal', coalesce((to_jsonb(c)->>'conperiod')::boolean, false),
343
343
 
344
- -- NEW: propagated-to-partition tagging (PG15+)
345
- 'is_partition_clone', (c.conparentid <> 0::oid),
344
+ -- Inherited from a parent (partition or classical inheritance).
345
+ -- coninhcount > 0 is the canonical signal across every constraint
346
+ -- kind. We previously used conparentid <> 0, but PostgreSQL only
347
+ -- populates conparentid for PK / UNIQUE / FK on partitions; CHECK
348
+ -- constraints on partitions always have conparentid = 0 and were
349
+ -- being re-emitted on every child, failing apply with 42710.
350
+ 'is_partition_clone', (c.coninhcount > 0),
346
351
  'parent_constraint_schema', case when c.conparentid <> 0::oid then pc.connamespace::regnamespace::text end,
347
352
  'parent_constraint_name', case when c.conparentid <> 0::oid then quote_ident(pc.conname) end,
348
353
  'parent_table_schema', case when c.conparentid <> 0::oid then pc_rel.relnamespace::regnamespace::text end,
@@ -689,7 +689,7 @@ describe("sql formatting snapshots", () => {
689
689
  COMMENT ON SUBSCRIPTION sub_replica IS NULL;
690
690
 
691
691
  -- fdw.create
692
- CREATE FOREIGN DATA WRAPPER postgres_fdw HANDLER postgres_fdw_handler VALIDATOR postgres_fdw_validator OPTIONS (debug 'true');
692
+ CREATE FOREIGN DATA WRAPPER postgres_fdw HANDLER postgres_fdw_handler VALIDATOR postgres_fdw_validator OPTIONS (debug '__OPTION_DEBUG__');
693
693
 
694
694
  -- fdw.drop
695
695
  DROP FOREIGN DATA WRAPPER postgres_fdw;
@@ -698,7 +698,7 @@ describe("sql formatting snapshots", () => {
698
698
  ALTER FOREIGN DATA WRAPPER postgres_fdw OWNER TO new_owner;
699
699
 
700
700
  -- fdw.alter.set_options
701
- ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (SET debug 'false', ADD use_remote_estimate '');
701
+ ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (SET debug '__OPTION_DEBUG__', ADD use_remote_estimate '');
702
702
 
703
703
  -- fdw.comment
704
704
  COMMENT ON FOREIGN DATA WRAPPER postgres_fdw IS 'PostgreSQL foreign data wrapper';
@@ -794,13 +794,13 @@ describe("sql formatting snapshots", () => {
794
794
  REVOKE GRANT OPTION FOR ALL ON SERVER remote_server FROM app_user;
795
795
 
796
796
  -- user_mapping.create
797
- CREATE USER MAPPING FOR app_user SERVER remote_server OPTIONS (user 'remote_app', password 'secret123');
797
+ CREATE USER MAPPING FOR app_user SERVER remote_server OPTIONS (user 'remote_app', password '__OPTION_PASSWORD__');
798
798
 
799
799
  -- user_mapping.drop
800
800
  DROP USER MAPPING FOR app_user SERVER remote_server;
801
801
 
802
802
  -- user_mapping.alter.set_options
803
- ALTER USER MAPPING FOR app_user SERVER remote_server OPTIONS (SET password 'new_secret');"
803
+ ALTER USER MAPPING FOR app_user SERVER remote_server OPTIONS (SET password '__OPTION_PASSWORD__');"
804
804
  `);
805
805
  });
806
806
  });
@@ -912,7 +912,7 @@ describe("sql formatting snapshots", () => {
912
912
  create foreign data wrapper postgres_fdw
913
913
  handler postgres_fdw_handler
914
914
  validator postgres_fdw_validator
915
- options (debug 'true');
915
+ options (debug '__OPTION_DEBUG__');
916
916
 
917
917
  -- fdw.drop
918
918
  drop foreign data wrapper postgres_fdw;
@@ -924,7 +924,7 @@ describe("sql formatting snapshots", () => {
924
924
  -- fdw.alter.set_options
925
925
  alter foreign data wrapper postgres_fdw
926
926
  options (
927
- SET debug 'false'
927
+ SET debug '__OPTION_DEBUG__'
928
928
  , ADD use_remote_estimate ''
929
929
  );
930
930
 
@@ -1049,13 +1049,13 @@ describe("sql formatting snapshots", () => {
1049
1049
 
1050
1050
  -- user_mapping.create
1051
1051
  create user mapping for app_user server remote_server
1052
- options (user 'remote_app', password 'secret123');
1052
+ options (user 'remote_app', password '__OPTION_PASSWORD__');
1053
1053
 
1054
1054
  -- user_mapping.drop
1055
1055
  drop user mapping for app_user server remote_server;
1056
1056
 
1057
1057
  -- user_mapping.alter.set_options
1058
- alter user mapping for app_user server remote_server options (SET password 'new_secret');"
1058
+ alter user mapping for app_user server remote_server options (SET password '__OPTION_PASSWORD__');"
1059
1059
  `);
1060
1060
  });
1061
1061
  });
@@ -1094,7 +1094,7 @@ describe("sql formatting snapshots", () => {
1094
1094
  CREATE FOREIGN DATA WRAPPER postgres_fdw
1095
1095
  HANDLER postgres_fdw_handler
1096
1096
  VALIDATOR postgres_fdw_validator
1097
- OPTIONS (debug 'true');
1097
+ OPTIONS (debug '__OPTION_DEBUG__');
1098
1098
 
1099
1099
  -- fdw.drop
1100
1100
  DROP FOREIGN DATA WRAPPER postgres_fdw;
@@ -1106,7 +1106,7 @@ describe("sql formatting snapshots", () => {
1106
1106
  -- fdw.alter.set_options
1107
1107
  ALTER FOREIGN DATA WRAPPER postgres_fdw
1108
1108
  OPTIONS (
1109
- SET debug 'false',
1109
+ SET debug '__OPTION_DEBUG__',
1110
1110
  ADD use_remote_estimate ''
1111
1111
  );
1112
1112
 
@@ -1264,7 +1264,7 @@ describe("sql formatting snapshots", () => {
1264
1264
  remote_server
1265
1265
  OPTIONS
1266
1266
  (user 'remote_app', password
1267
- 'secret123');
1267
+ '__OPTION_PASSWORD__');
1268
1268
 
1269
1269
  -- user_mapping.drop
1270
1270
  DROP USER MAPPING FOR app_user SERVER
@@ -1273,7 +1273,8 @@ describe("sql formatting snapshots", () => {
1273
1273
  -- user_mapping.alter.set_options
1274
1274
  ALTER USER MAPPING FOR app_user SERVER
1275
1275
  remote_server
1276
- OPTIONS (SET password 'new_secret');"
1276
+ OPTIONS
1277
+ (SET password '__OPTION_PASSWORD__');"
1277
1278
  `);
1278
1279
  });
1279
1280
  });
@@ -908,7 +908,7 @@ describe("sql formatting snapshots", () => {
908
908
  CREATE FOREIGN DATA WRAPPER postgres_fdw
909
909
  HANDLER postgres_fdw_handler
910
910
  VALIDATOR postgres_fdw_validator
911
- OPTIONS (debug 'true');
911
+ OPTIONS (debug '__OPTION_DEBUG__');
912
912
 
913
913
  -- fdw.drop
914
914
  DROP FOREIGN DATA WRAPPER postgres_fdw;
@@ -920,7 +920,7 @@ describe("sql formatting snapshots", () => {
920
920
  -- fdw.alter.set_options
921
921
  ALTER FOREIGN DATA WRAPPER postgres_fdw
922
922
  OPTIONS (
923
- SET debug 'false',
923
+ SET debug '__OPTION_DEBUG__',
924
924
  ADD use_remote_estimate ''
925
925
  );
926
926
 
@@ -1045,13 +1045,13 @@ describe("sql formatting snapshots", () => {
1045
1045
 
1046
1046
  -- user_mapping.create
1047
1047
  CREATE USER MAPPING FOR app_user SERVER remote_server
1048
- OPTIONS (user 'remote_app', password 'secret123');
1048
+ OPTIONS (user 'remote_app', password '__OPTION_PASSWORD__');
1049
1049
 
1050
1050
  -- user_mapping.drop
1051
1051
  DROP USER MAPPING FOR app_user SERVER remote_server;
1052
1052
 
1053
1053
  -- user_mapping.alter.set_options
1054
- ALTER USER MAPPING FOR app_user SERVER remote_server OPTIONS (SET password 'new_secret');"
1054
+ ALTER USER MAPPING FOR app_user SERVER remote_server OPTIONS (SET password '__OPTION_PASSWORD__');"
1055
1055
  `);
1056
1056
  });
1057
1057
  });
@@ -899,7 +899,7 @@ describe("sql formatting snapshots", () => {
899
899
  CREATE FOREIGN DATA WRAPPER postgres_fdw
900
900
  HANDLER postgres_fdw_handler
901
901
  VALIDATOR postgres_fdw_validator
902
- OPTIONS (debug 'true');
902
+ OPTIONS (debug '__OPTION_DEBUG__');
903
903
 
904
904
  -- fdw.drop
905
905
  DROP FOREIGN DATA WRAPPER postgres_fdw;
@@ -911,7 +911,7 @@ describe("sql formatting snapshots", () => {
911
911
  -- fdw.alter.set_options
912
912
  ALTER FOREIGN DATA WRAPPER postgres_fdw
913
913
  OPTIONS (
914
- SET debug 'false',
914
+ SET debug '__OPTION_DEBUG__',
915
915
  ADD use_remote_estimate ''
916
916
  );
917
917
 
@@ -1036,13 +1036,13 @@ describe("sql formatting snapshots", () => {
1036
1036
 
1037
1037
  -- user_mapping.create
1038
1038
  CREATE USER MAPPING FOR app_user SERVER remote_server
1039
- OPTIONS (user 'remote_app', password 'secret123');
1039
+ OPTIONS (user 'remote_app', password '__OPTION_PASSWORD__');
1040
1040
 
1041
1041
  -- user_mapping.drop
1042
1042
  DROP USER MAPPING FOR app_user SERVER remote_server;
1043
1043
 
1044
1044
  -- user_mapping.alter.set_options
1045
- ALTER USER MAPPING FOR app_user SERVER remote_server OPTIONS (SET password 'new_secret');"
1045
+ ALTER USER MAPPING FOR app_user SERVER remote_server OPTIONS (SET password '__OPTION_PASSWORD__');"
1046
1046
  `);
1047
1047
  });
1048
1048
  });
@@ -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