@supabase/pg-delta 1.0.0-alpha.20 → 1.0.0-alpha.21

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 (28) hide show
  1. package/dist/core/catalog.diff.js +0 -1
  2. package/dist/core/objects/publication/changes/publication.alter.d.ts +1 -1
  3. package/dist/core/objects/sequence/sequence.diff.js +13 -5
  4. package/dist/core/objects/table/changes/table.alter.d.ts +4 -0
  5. package/dist/core/objects/table/changes/table.alter.js +19 -4
  6. package/dist/core/objects/table/table.diff.js +21 -2
  7. package/dist/core/objects/table/table.model.js +10 -7
  8. package/dist/core/post-diff-cycle-breaking.d.ts +21 -21
  9. package/dist/core/post-diff-cycle-breaking.js +24 -133
  10. package/dist/core/sort/cycle-breakers.d.ts +15 -0
  11. package/dist/core/sort/cycle-breakers.js +269 -0
  12. package/dist/core/sort/sort-changes.js +97 -43
  13. package/package.json +1 -1
  14. package/src/core/catalog.diff.ts +0 -1
  15. package/src/core/expand-replace-dependencies.test.ts +8 -5
  16. package/src/core/objects/publication/changes/publication.alter.ts +1 -1
  17. package/src/core/objects/sequence/sequence.diff.test.ts +6 -1
  18. package/src/core/objects/sequence/sequence.diff.ts +12 -4
  19. package/src/core/objects/table/changes/table.alter.test.ts +13 -2
  20. package/src/core/objects/table/changes/table.alter.ts +36 -7
  21. package/src/core/objects/table/table.diff.test.ts +43 -0
  22. package/src/core/objects/table/table.diff.ts +28 -4
  23. package/src/core/objects/table/table.model.ts +10 -7
  24. package/src/core/post-diff-cycle-breaking.test.ts +0 -156
  25. package/src/core/post-diff-cycle-breaking.ts +23 -202
  26. package/src/core/sort/cycle-breakers.test.ts +476 -0
  27. package/src/core/sort/cycle-breakers.ts +311 -0
  28. package/src/core/sort/sort-changes.ts +135 -50
@@ -556,11 +556,21 @@ export class AlterTableDropColumn extends AlterTableChange {
556
556
  public readonly table: Table;
557
557
  public readonly column: ColumnProps;
558
558
  public readonly scope = "object" as const;
559
-
560
- constructor(props: { table: Table; column: ColumnProps }) {
559
+ // Drop the implicit `requires(table)` edge. Only set by the lazy
560
+ // cycle-breaker for the publication↔column case, where the table survives
561
+ // the migration and the edge is therefore artificial. See
562
+ // `sort/cycle-breakers.ts` for the full justification.
563
+ public readonly omitTableRequirement: boolean;
564
+
565
+ constructor(props: {
566
+ table: Table;
567
+ column: ColumnProps;
568
+ omitTableRequirement?: boolean;
569
+ }) {
561
570
  super();
562
571
  this.table = props.table;
563
572
  this.column = props.column;
573
+ this.omitTableRequirement = props.omitTableRequirement ?? false;
564
574
  }
565
575
 
566
576
  get drops() {
@@ -570,10 +580,12 @@ export class AlterTableDropColumn extends AlterTableChange {
570
580
  }
571
581
 
572
582
  get requires() {
573
- return [
574
- this.table.stableId,
575
- stableId.column(this.table.schema, this.table.name, this.column.name),
576
- ];
583
+ const colId = stableId.column(
584
+ this.table.schema,
585
+ this.table.name,
586
+ this.column.name,
587
+ );
588
+ return this.omitTableRequirement ? [colId] : [this.table.stableId, colId];
577
589
  }
578
590
 
579
591
  serialize(_options?: SerializeOptions): string {
@@ -592,12 +604,18 @@ export class AlterTableDropColumn extends AlterTableChange {
592
604
  export class AlterTableAlterColumnType extends AlterTableChange {
593
605
  public readonly table: Table;
594
606
  public readonly column: ColumnProps;
607
+ public readonly previousColumn?: ColumnProps;
595
608
  public readonly scope = "object" as const;
596
609
 
597
- constructor(props: { table: Table; column: ColumnProps }) {
610
+ constructor(props: {
611
+ table: Table;
612
+ column: ColumnProps;
613
+ previousColumn?: ColumnProps;
614
+ }) {
598
615
  super();
599
616
  this.table = props.table;
600
617
  this.column = props.column;
618
+ this.previousColumn = props.previousColumn;
601
619
  }
602
620
 
603
621
  get requires() {
@@ -607,6 +625,14 @@ export class AlterTableAlterColumnType extends AlterTableChange {
607
625
  }
608
626
 
609
627
  serialize(_options?: SerializeOptions): string {
628
+ // previousColumn is optional so direct serializer tests/fixtures can keep
629
+ // emitting canonical ALTER TYPE SQL without forcing a USING expression.
630
+ // When provided, we can detect true type changes and add USING for casts
631
+ // PostgreSQL cannot perform automatically.
632
+ const hasTypeChangedWithPreviousDefinition =
633
+ this.previousColumn?.data_type_str !== undefined &&
634
+ this.previousColumn.data_type_str !== this.column.data_type_str;
635
+
610
636
  const parts: string[] = [
611
637
  "ALTER TABLE",
612
638
  `${this.table.schema}.${this.table.name}`,
@@ -618,6 +644,9 @@ export class AlterTableAlterColumnType extends AlterTableChange {
618
644
  if (this.column.collation) {
619
645
  parts.push("COLLATE", this.column.collation);
620
646
  }
647
+ if (hasTypeChangedWithPreviousDefinition) {
648
+ parts.push("USING", `${this.column.name}::${this.column.data_type_str}`);
649
+ }
621
650
  return parts.join(" ");
622
651
  }
623
652
  }
@@ -767,6 +767,9 @@ describe.concurrent("table.diff", () => {
767
767
  expect(
768
768
  typeChanges.some((c) => c instanceof AlterTableAlterColumnType),
769
769
  ).toBe(true);
770
+ expect(typeChanges.map((c) => c.serialize())).toContain(
771
+ "ALTER TABLE public.t2 ALTER COLUMN a TYPE text USING a::text",
772
+ );
770
773
 
771
774
  const defaultAdded = new Table({
772
775
  ...base,
@@ -817,6 +820,46 @@ describe.concurrent("table.diff", () => {
817
820
  expect(
818
821
  notNullDropped.some((c) => c instanceof AlterTableAlterColumnDropNotNull),
819
822
  ).toBe(true);
823
+
824
+ const withDefault = new Table({
825
+ ...base,
826
+ name: "t2",
827
+ columns: [
828
+ {
829
+ ...withCol.columns[0],
830
+ data_type: "text",
831
+ data_type_str: "text",
832
+ default: "'active'",
833
+ },
834
+ ],
835
+ });
836
+ const typeChangedWithDefault = new Table({
837
+ ...base,
838
+ name: "t2",
839
+ columns: [
840
+ {
841
+ ...withDefault.columns[0],
842
+ data_type: "USER-DEFINED",
843
+ data_type_str: "test_schema.status",
844
+ is_custom_type: true,
845
+ custom_type_type: "e",
846
+ custom_type_category: "E",
847
+ custom_type_schema: "test_schema",
848
+ custom_type_name: "status",
849
+ default: "'active'::test_schema.status",
850
+ },
851
+ ],
852
+ });
853
+ const typeChangesWithDefault = diffTables(
854
+ testContext,
855
+ { [withDefault.stableId]: withDefault },
856
+ { [typeChangedWithDefault.stableId]: typeChangedWithDefault },
857
+ );
858
+ expect(typeChangesWithDefault.map((c) => c.serialize())).toEqual([
859
+ "ALTER TABLE public.t2 ALTER COLUMN a DROP DEFAULT",
860
+ "ALTER TABLE public.t2 ALTER COLUMN a TYPE test_schema.status USING a::test_schema.status",
861
+ "ALTER TABLE public.t2 ALTER COLUMN a SET DEFAULT 'active'::test_schema.status",
862
+ ]);
820
863
  });
821
864
 
822
865
  test("identity transitions emit drop/add/set-generated changes", () => {
@@ -701,19 +701,39 @@ export function diffTables(
701
701
  const branchCol = branchCols.get(name);
702
702
  if (!branchCol) continue;
703
703
 
704
+ const columnTypeChanged =
705
+ mainCol.data_type_str !== branchCol.data_type_str;
706
+ const columnCollationChanged = mainCol.collation !== branchCol.collation;
707
+ const needsDefaultSafeFlow =
708
+ columnTypeChanged && mainCol.default !== null;
709
+
704
710
  // TYPE or COLLATION change
705
- if (
706
- mainCol.data_type_str !== branchCol.data_type_str ||
707
- mainCol.collation !== branchCol.collation
708
- ) {
711
+ if (columnTypeChanged || columnCollationChanged) {
709
712
  // Skip if parent has the same type/collation change
710
713
  if (!parentHasSameColumnPropertyChange(name, "type")) {
714
+ if (needsDefaultSafeFlow) {
715
+ changes.push(
716
+ new AlterTableAlterColumnDropDefault({
717
+ table: branchTable,
718
+ column: branchCol,
719
+ }),
720
+ );
721
+ }
711
722
  changes.push(
712
723
  new AlterTableAlterColumnType({
713
724
  table: branchTable,
714
725
  column: branchCol,
726
+ previousColumn: mainCol,
715
727
  }),
716
728
  );
729
+ if (needsDefaultSafeFlow && branchCol.default !== null) {
730
+ changes.push(
731
+ new AlterTableAlterColumnSetDefault({
732
+ table: branchTable,
733
+ column: branchCol,
734
+ }),
735
+ );
736
+ }
717
737
  }
718
738
  }
719
739
 
@@ -734,6 +754,10 @@ export function diffTables(
734
754
  if (mainCol.default !== branchCol.default) {
735
755
  // Skip if parent has the same default change
736
756
  if (!parentHasSameColumnPropertyChange(name, "default")) {
757
+ if (needsDefaultSafeFlow) {
758
+ // Defaults were already dropped/re-set in the type-change flow above.
759
+ continue;
760
+ }
737
761
  if (branchCol.default === null) {
738
762
  // Drop default value
739
763
  changes.push(
@@ -296,13 +296,16 @@ select
296
296
 
297
297
  'key_columns',
298
298
  case
299
- when c.conkey is not null then (
300
- select json_agg(quote_ident(att.attname) order by pk.ordinality)
301
- from unnest(c.conkey) with ordinality as pk(attnum, ordinality)
302
- join pg_attribute att
303
- on att.attrelid = c.conrelid
304
- and att.attnum = pk.attnum
305
- and att.attisdropped = false
299
+ when c.conkey is not null then coalesce(
300
+ (
301
+ select json_agg(quote_ident(att.attname) order by pk.ordinality)
302
+ from unnest(c.conkey) with ordinality as pk(attnum, ordinality)
303
+ join pg_attribute att
304
+ on att.attrelid = c.conrelid
305
+ and att.attnum = pk.attnum
306
+ and att.attisdropped = false
307
+ ),
308
+ '[]'::json
306
309
  )
307
310
  else '[]'::json
308
311
  end,
@@ -1,5 +1,4 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { Catalog, createEmptyCatalog } from "./catalog.model.ts";
3
2
  import type { Change } from "./change.types.ts";
4
3
  import {
5
4
  AlterTableAddConstraint,
@@ -15,7 +14,6 @@ import { CreateTable } from "./objects/table/changes/table.create.ts";
15
14
  import { DropTable } from "./objects/table/changes/table.drop.ts";
16
15
  import { GrantTablePrivileges } from "./objects/table/changes/table.privilege.ts";
17
16
  import { Table } from "./objects/table/table.model.ts";
18
- import { stableId } from "./objects/utils.ts";
19
17
  import { normalizePostDiffCycles } from "./post-diff-cycle-breaking.ts";
20
18
 
21
19
  const baseTableProps = {
@@ -62,149 +60,7 @@ function integerColumn(name: string, position: number) {
62
60
  }
63
61
 
64
62
  describe("normalizePostDiffCycles", () => {
65
- test("injects explicit FK drops for mutually dependent dropped tables", async () => {
66
- const baseline = await createEmptyCatalog(170000, "postgres");
67
- const tableA = new Table({
68
- ...baseTableProps,
69
- name: "a",
70
- columns: [
71
- { ...integerColumn("id", 1), not_null: true },
72
- integerColumn("b_id", 2),
73
- ],
74
- constraints: [
75
- {
76
- name: "a_b_fkey",
77
- constraint_type: "f",
78
- deferrable: false,
79
- initially_deferred: false,
80
- validated: true,
81
- is_local: true,
82
- no_inherit: false,
83
- is_temporal: false,
84
- is_partition_clone: false,
85
- parent_constraint_schema: null,
86
- parent_constraint_name: null,
87
- parent_table_schema: null,
88
- parent_table_name: null,
89
- key_columns: ["b_id"],
90
- foreign_key_columns: ["id"],
91
- foreign_key_table: "b",
92
- foreign_key_schema: "public",
93
- foreign_key_table_is_partition: false,
94
- foreign_key_parent_schema: null,
95
- foreign_key_parent_table: null,
96
- foreign_key_effective_schema: "public",
97
- foreign_key_effective_table: "b",
98
- on_update: "a",
99
- on_delete: "a",
100
- match_type: "s",
101
- check_expression: null,
102
- owner: "postgres",
103
- definition: "FOREIGN KEY (b_id) REFERENCES public.b(id)",
104
- comment: null,
105
- },
106
- ],
107
- });
108
- const tableB = new Table({
109
- ...baseTableProps,
110
- name: "b",
111
- columns: [
112
- { ...integerColumn("id", 1), not_null: true },
113
- integerColumn("a_id", 2),
114
- ],
115
- constraints: [
116
- {
117
- name: "b_a_fkey",
118
- constraint_type: "f",
119
- deferrable: false,
120
- initially_deferred: false,
121
- validated: true,
122
- is_local: true,
123
- no_inherit: false,
124
- is_temporal: false,
125
- is_partition_clone: false,
126
- parent_constraint_schema: null,
127
- parent_constraint_name: null,
128
- parent_table_schema: null,
129
- parent_table_name: null,
130
- key_columns: ["a_id"],
131
- foreign_key_columns: ["id"],
132
- foreign_key_table: "a",
133
- foreign_key_schema: "public",
134
- foreign_key_table_is_partition: false,
135
- foreign_key_parent_schema: null,
136
- foreign_key_parent_table: null,
137
- foreign_key_effective_schema: "public",
138
- foreign_key_effective_table: "a",
139
- on_update: "a",
140
- on_delete: "a",
141
- match_type: "s",
142
- check_expression: null,
143
- owner: "postgres",
144
- definition: "FOREIGN KEY (a_id) REFERENCES public.a(id)",
145
- comment: null,
146
- },
147
- ],
148
- });
149
- const mainCatalog = new Catalog({
150
- ...baseline,
151
- tables: {
152
- [tableA.stableId]: tableA,
153
- [tableB.stableId]: tableB,
154
- },
155
- });
156
- const changes: Change[] = [
157
- new DropTable({ table: tableA }),
158
- new DropTable({ table: tableB }),
159
- ];
160
-
161
- const normalized = normalizePostDiffCycles({
162
- changes,
163
- mainCatalog,
164
- });
165
-
166
- const explicitConstraintDrops = normalized.filter(
167
- (change) => change instanceof AlterTableDropConstraint,
168
- );
169
- expect(explicitConstraintDrops).toHaveLength(2);
170
-
171
- const normalizedDropTableA = normalized.find(
172
- (change) =>
173
- change instanceof DropTable &&
174
- change.table.stableId === tableA.stableId,
175
- );
176
- const normalizedDropTableB = normalized.find(
177
- (change) =>
178
- change instanceof DropTable &&
179
- change.table.stableId === tableB.stableId,
180
- );
181
- if (!(normalizedDropTableA instanceof DropTable)) {
182
- throw new Error("expected normalized DropTable(public.a)");
183
- }
184
- if (!(normalizedDropTableB instanceof DropTable)) {
185
- throw new Error("expected normalized DropTable(public.b)");
186
- }
187
-
188
- expect(
189
- normalizedDropTableA.externallyDroppedConstraints.has("a_b_fkey"),
190
- ).toBe(true);
191
- expect(
192
- normalizedDropTableB.externallyDroppedConstraints.has("b_a_fkey"),
193
- ).toBe(true);
194
- expect(
195
- normalizedDropTableA.requires.includes(
196
- stableId.constraint("public", "a", "a_b_fkey"),
197
- ),
198
- ).toBe(false);
199
- expect(
200
- normalizedDropTableB.requires.includes(
201
- stableId.constraint("public", "b", "b_a_fkey"),
202
- ),
203
- ).toBe(false);
204
- });
205
-
206
63
  test("prunes same-table drop-column and drop-constraint ALTERs for replaced tables only", async () => {
207
- const baseline = await createEmptyCatalog(170000, "postgres");
208
64
  const mainChildren = new Table({
209
65
  ...baseTableProps,
210
66
  name: "children",
@@ -292,14 +148,9 @@ describe("normalizePostDiffCycles", () => {
292
148
  preExistingReplicaIdentity,
293
149
  preExistingGrant,
294
150
  ];
295
- const mainCatalog = new Catalog({
296
- ...baseline,
297
- tables: { [mainChildren.stableId]: mainChildren },
298
- });
299
151
 
300
152
  const normalized = normalizePostDiffCycles({
301
153
  changes,
302
- mainCatalog,
303
154
  replacedTableIds: new Set([mainChildren.stableId]),
304
155
  });
305
156
 
@@ -322,7 +173,6 @@ describe("normalizePostDiffCycles", () => {
322
173
  });
323
174
 
324
175
  test("dedupes duplicate constraint Add/Validate/Comment on replaced tables keeping last occurrence", async () => {
325
- const baseline = await createEmptyCatalog(170000, "postgres");
326
176
  const branchChildren = new Table({
327
177
  ...baseTableProps,
328
178
  name: "children",
@@ -423,14 +273,8 @@ describe("normalizePostDiffCycles", () => {
423
273
  expansionComment,
424
274
  ];
425
275
 
426
- const mainCatalog = new Catalog({
427
- ...baseline,
428
- tables: { [branchChildren.stableId]: branchChildren },
429
- });
430
-
431
276
  const normalized = normalizePostDiffCycles({
432
277
  changes,
433
- mainCatalog,
434
278
  replacedTableIds: new Set([branchChildren.stableId]),
435
279
  });
436
280
 
@@ -1,4 +1,3 @@
1
- import type { Catalog } from "./catalog.model.ts";
2
1
  import type { Change } from "./change.types.ts";
3
2
  import {
4
3
  AlterTableAddConstraint,
@@ -7,7 +6,6 @@ import {
7
6
  AlterTableValidateConstraint,
8
7
  } from "./objects/table/changes/table.alter.ts";
9
8
  import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.ts";
10
- import { DropTable } from "./objects/table/changes/table.drop.ts";
11
9
  import { stableId } from "./objects/utils.ts";
12
10
 
13
11
  function constraintStableId(
@@ -17,31 +15,6 @@ function constraintStableId(
17
15
  return stableId.constraint(table.schema, table.name, constraintName);
18
16
  }
19
17
 
20
- /**
21
- * Yield FK constraints on `table` whose referenced table is also dropped in the
22
- * final plan. Self-references are left alone because the sort phase already
23
- * handles the resulting self-loop correctly.
24
- */
25
- function* iterCrossDropFkConstraints(
26
- table: Catalog["tables"][string],
27
- droppedSet: ReadonlySet<string>,
28
- ) {
29
- for (const constraint of table.constraints) {
30
- if (constraint.constraint_type !== "f") continue;
31
- if (constraint.is_partition_clone) continue;
32
- if (!constraint.foreign_key_schema || !constraint.foreign_key_table) {
33
- continue;
34
- }
35
- const referencedId = stableId.table(
36
- constraint.foreign_key_schema,
37
- constraint.foreign_key_table,
38
- );
39
- if (referencedId === table.stableId) continue;
40
- if (!droppedSet.has(referencedId)) continue;
41
- yield { constraint, referencedId };
42
- }
43
- }
44
-
45
18
  function isSupersededByTableReplacement(
46
19
  change: Change,
47
20
  replacedTableIds: ReadonlySet<string>,
@@ -121,59 +94,35 @@ function dropReplacedTableDuplicateConstraintChanges(
121
94
  return mutated ? reversedKept.reverse() : changes;
122
95
  }
123
96
 
124
- function collectExplicitConstraintDropIds(changes: Change[]) {
125
- const explicitConstraintDropIds = new Set<string>();
126
-
127
- for (const change of changes) {
128
- if (!(change instanceof AlterTableDropConstraint)) continue;
129
- explicitConstraintDropIds.add(
130
- constraintStableId(change.table, change.constraint.name),
131
- );
132
- }
133
-
134
- return explicitConstraintDropIds;
135
- }
136
-
137
- function hasSameEntries(
138
- left: ReadonlySet<string>,
139
- right: ReadonlySet<string>,
140
- ): boolean {
141
- if (left.size !== right.size) return false;
142
- for (const value of left) {
143
- if (!right.has(value)) return false;
144
- }
145
- return true;
146
- }
147
-
148
97
  /**
149
- * Normalize change-list cycles that only become apparent after all object
150
- * diffs have been collected.
98
+ * Apply structural rewrites to the change list that are only obvious once
99
+ * every object diff has been collected. This pass does NOT prevent dependency
100
+ * cycles — that responsibility now lives in the sort phase, where
101
+ * `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
102
+ * that edge filtering can't break (FK SCC of dropped tables,
103
+ * AlterPublicationDropTables ↔ AlterTableDropColumn, …).
104
+ *
105
+ * Concretely, this pass:
151
106
  *
152
- * This pass intentionally handles whole-plan interactions only:
153
- * - If replace expansion added `DropTable(T)+CreateTable(T)`, targeted
154
- * `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)` changes are
155
- * redundant and create an unbreakable drop-phase cycle, so we elide them.
156
- * - When the same `DropTable+CreateTable` pair is present, the expansion
157
- * also emits one `AlterTableAddConstraint` / `AlterTableValidateConstraint`
158
- * / `CreateCommentOnConstraint` per branch constraint, which may collide
159
- * with the same change already emitted by `diffTables()` (for example on a
160
- * shape flip or a new constraint). We dedupe these keeping only the last
161
- * occurrence so the expansion's emission survives and the diffTables
162
- * duplicate is removed.
163
- * - If two dropped tables reference each other via FK, we insert dedicated
164
- * `AlterTableDropConstraint` changes and teach the paired `DropTable`
165
- * changes not to claim those FK stable IDs.
107
+ * - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
108
+ * changes that are made redundant by an expansion-emitted
109
+ * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
110
+ * would try to drop a column that no longer exists in the freshly
111
+ * recreated table.
112
+ * - Dedupes duplicate `AlterTableAddConstraint` /
113
+ * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
114
+ * produced when `diffTables()` and `expandReplaceDependencies()` both
115
+ * emit the same constraint operation for a replaced table. Last write
116
+ * wins so the expansion's emission survives.
166
117
  *
167
- * Object-local PostgreSQL semantics (for example owned-sequence cascades) stay
168
- * in the corresponding `diff*` function instead of this pass.
118
+ * Object-local PostgreSQL semantics (for example owned-sequence cascades)
119
+ * stay in the corresponding `diff*` function instead of this pass.
169
120
  */
170
121
  export function normalizePostDiffCycles({
171
122
  changes,
172
- mainCatalog,
173
123
  replacedTableIds = new Set<string>(),
174
124
  }: {
175
125
  changes: Change[];
176
- mainCatalog: Catalog;
177
126
  replacedTableIds?: ReadonlySet<string>;
178
127
  }): Change[] {
179
128
  const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(
@@ -181,137 +130,9 @@ export function normalizePostDiffCycles({
181
130
  replacedTableIds,
182
131
  );
183
132
 
184
- const structurallyNormalizedChanges =
185
- replacedTableIds.size === 0
186
- ? dedupedChanges
187
- : dedupedChanges.filter(
188
- (change) => !isSupersededByTableReplacement(change, replacedTableIds),
189
- );
133
+ if (replacedTableIds.size === 0) return dedupedChanges;
190
134
 
191
- const dropTableChanges = structurallyNormalizedChanges.filter(
192
- (change): change is DropTable => change instanceof DropTable,
135
+ return dedupedChanges.filter(
136
+ (change) => !isSupersededByTableReplacement(change, replacedTableIds),
193
137
  );
194
-
195
- if (dropTableChanges.length < 2) {
196
- return structurallyNormalizedChanges;
197
- }
198
-
199
- const droppedSet = new Set(
200
- dropTableChanges.map((change) => change.table.stableId),
201
- );
202
- const droppedFkTargets = new Map<string, Set<string>>();
203
-
204
- for (const dropTableChange of dropTableChanges) {
205
- const mainTable =
206
- mainCatalog.tables[dropTableChange.table.stableId] ??
207
- dropTableChange.table;
208
- const targets = new Set<string>();
209
-
210
- for (const { referencedId } of iterCrossDropFkConstraints(
211
- mainTable,
212
- droppedSet,
213
- )) {
214
- targets.add(referencedId);
215
- }
216
-
217
- droppedFkTargets.set(mainTable.stableId, targets);
218
- }
219
-
220
- const explicitConstraintDropIds = collectExplicitConstraintDropIds(
221
- structurallyNormalizedChanges,
222
- );
223
- const injectedConstraintDropsByTableId = new Map<
224
- string,
225
- AlterTableDropConstraint[]
226
- >();
227
- const externallyDroppedConstraintsByTableId = new Map<
228
- string,
229
- ReadonlySet<string>
230
- >();
231
- let didMutate = structurallyNormalizedChanges !== changes;
232
-
233
- for (const dropTableChange of dropTableChanges) {
234
- const mainTable =
235
- mainCatalog.tables[dropTableChange.table.stableId] ??
236
- dropTableChange.table;
237
- const externallyDroppedConstraints = new Set(
238
- dropTableChange.externallyDroppedConstraints,
239
- );
240
-
241
- for (const { constraint, referencedId } of iterCrossDropFkConstraints(
242
- mainTable,
243
- droppedSet,
244
- )) {
245
- const isMutual =
246
- droppedFkTargets.get(referencedId)?.has(mainTable.stableId) === true;
247
- if (!isMutual) continue;
248
-
249
- const droppedConstraintStableId = constraintStableId(
250
- mainTable,
251
- constraint.name,
252
- );
253
- externallyDroppedConstraints.add(constraint.name);
254
-
255
- if (!explicitConstraintDropIds.has(droppedConstraintStableId)) {
256
- const injectedDrop = new AlterTableDropConstraint({
257
- table: mainTable,
258
- constraint,
259
- });
260
- const existingDrops =
261
- injectedConstraintDropsByTableId.get(mainTable.stableId) ?? [];
262
- existingDrops.push(injectedDrop);
263
- injectedConstraintDropsByTableId.set(mainTable.stableId, existingDrops);
264
- explicitConstraintDropIds.add(droppedConstraintStableId);
265
- didMutate = true;
266
- }
267
- }
268
-
269
- if (
270
- !hasSameEntries(
271
- dropTableChange.externallyDroppedConstraints,
272
- externallyDroppedConstraints,
273
- )
274
- ) {
275
- externallyDroppedConstraintsByTableId.set(
276
- mainTable.stableId,
277
- externallyDroppedConstraints,
278
- );
279
- didMutate = true;
280
- }
281
- }
282
-
283
- if (!didMutate) {
284
- return changes;
285
- }
286
-
287
- const normalizedChanges: Change[] = [];
288
-
289
- for (const change of structurallyNormalizedChanges) {
290
- if (!(change instanceof DropTable)) {
291
- normalizedChanges.push(change);
292
- continue;
293
- }
294
-
295
- const injectedConstraintDrops =
296
- injectedConstraintDropsByTableId.get(change.table.stableId) ?? [];
297
- if (injectedConstraintDrops.length > 0) {
298
- normalizedChanges.push(...injectedConstraintDrops);
299
- }
300
-
301
- const externallyDroppedConstraints =
302
- externallyDroppedConstraintsByTableId.get(change.table.stableId);
303
- if (!externallyDroppedConstraints) {
304
- normalizedChanges.push(change);
305
- continue;
306
- }
307
-
308
- normalizedChanges.push(
309
- new DropTable({
310
- table: change.table,
311
- externallyDroppedConstraints,
312
- }),
313
- );
314
- }
315
-
316
- return normalizedChanges;
317
138
  }