@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.
- package/dist/core/catalog.diff.js +0 -1
- package/dist/core/objects/publication/changes/publication.alter.d.ts +1 -1
- package/dist/core/objects/sequence/sequence.diff.js +13 -5
- package/dist/core/objects/table/changes/table.alter.d.ts +4 -0
- package/dist/core/objects/table/changes/table.alter.js +19 -4
- package/dist/core/objects/table/table.diff.js +21 -2
- package/dist/core/objects/table/table.model.js +10 -7
- package/dist/core/post-diff-cycle-breaking.d.ts +21 -21
- package/dist/core/post-diff-cycle-breaking.js +24 -133
- package/dist/core/sort/cycle-breakers.d.ts +15 -0
- package/dist/core/sort/cycle-breakers.js +269 -0
- package/dist/core/sort/sort-changes.js +97 -43
- package/package.json +1 -1
- package/src/core/catalog.diff.ts +0 -1
- package/src/core/expand-replace-dependencies.test.ts +8 -5
- package/src/core/objects/publication/changes/publication.alter.ts +1 -1
- package/src/core/objects/sequence/sequence.diff.test.ts +6 -1
- package/src/core/objects/sequence/sequence.diff.ts +12 -4
- package/src/core/objects/table/changes/table.alter.test.ts +13 -2
- package/src/core/objects/table/changes/table.alter.ts +36 -7
- package/src/core/objects/table/table.diff.test.ts +43 -0
- package/src/core/objects/table/table.diff.ts +28 -4
- package/src/core/objects/table/table.model.ts +10 -7
- package/src/core/post-diff-cycle-breaking.test.ts +0 -156
- package/src/core/post-diff-cycle-breaking.ts +23 -202
- package/src/core/sort/cycle-breakers.test.ts +476 -0
- package/src/core/sort/cycle-breakers.ts +311 -0
- 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
|
-
|
|
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
|
-
|
|
574
|
-
this.table.
|
|
575
|
-
|
|
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: {
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
*
|
|
150
|
-
*
|
|
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
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* `
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* / `CreateCommentOnConstraint`
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
(change)
|
|
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
|
}
|