@supabase/pg-delta 1.0.0-alpha.17 → 1.0.0-alpha.19
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/expand-replace-dependencies.js +69 -0
- package/dist/core/objects/index/index.model.js +12 -2
- package/dist/core/objects/procedure/procedure.diff.js +33 -20
- package/dist/core/objects/rls-policy/changes/rls-policy.create.js +23 -0
- package/dist/core/objects/rls-policy/rls-policy.model.d.ts +49 -0
- package/dist/core/objects/rls-policy/rls-policy.model.js +122 -1
- package/dist/core/objects/table/table.diff.js +1 -0
- package/dist/core/objects/table/table.model.d.ts +4 -0
- package/dist/core/objects/table/table.model.js +2 -0
- package/dist/core/plan/sql-format/fixtures.js +8 -0
- package/dist/core/post-diff-cycle-breaking.d.ts +7 -0
- package/dist/core/post-diff-cycle-breaking.js +69 -3
- package/package.json +1 -1
- package/src/core/catalog.snapshot.test.ts +2 -0
- package/src/core/expand-replace-dependencies.test.ts +118 -0
- package/src/core/expand-replace-dependencies.ts +78 -0
- package/src/core/objects/index/index.model.test.ts +83 -0
- package/src/core/objects/index/index.model.ts +13 -4
- package/src/core/objects/procedure/procedure.diff.test.ts +100 -2
- package/src/core/objects/procedure/procedure.diff.ts +39 -21
- package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +16 -0
- package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +128 -0
- package/src/core/objects/rls-policy/changes/rls-policy.create.ts +27 -0
- package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +2 -0
- package/src/core/objects/rls-policy/rls-policy.diff.test.ts +2 -0
- package/src/core/objects/rls-policy/rls-policy.model.ts +134 -1
- package/src/core/objects/table/changes/table.alter.test.ts +1 -0
- package/src/core/objects/table/table.diff.test.ts +102 -0
- package/src/core/objects/table/table.diff.ts +1 -0
- package/src/core/objects/table/table.model.ts +2 -0
- package/src/core/plan/sql-format/fixtures.ts +8 -0
- package/src/core/post-diff-cycle-breaking.test.ts +142 -0
- package/src/core/post-diff-cycle-breaking.ts +83 -2
|
@@ -112,6 +112,7 @@ describe.concurrent("table.diff", () => {
|
|
|
112
112
|
validated: false,
|
|
113
113
|
is_local: true,
|
|
114
114
|
no_inherit: false,
|
|
115
|
+
is_temporal: false,
|
|
115
116
|
is_partition_clone: false,
|
|
116
117
|
parent_constraint_schema: null,
|
|
117
118
|
parent_constraint_name: null,
|
|
@@ -328,6 +329,7 @@ describe.concurrent("table.diff", () => {
|
|
|
328
329
|
validated: false,
|
|
329
330
|
is_local: true,
|
|
330
331
|
no_inherit: false,
|
|
332
|
+
is_temporal: false,
|
|
331
333
|
is_partition_clone: false,
|
|
332
334
|
parent_constraint_schema: null,
|
|
333
335
|
parent_constraint_name: null,
|
|
@@ -453,6 +455,7 @@ describe.concurrent("table.diff", () => {
|
|
|
453
455
|
validated: true,
|
|
454
456
|
is_local: true,
|
|
455
457
|
no_inherit: false,
|
|
458
|
+
is_temporal: false,
|
|
456
459
|
is_partition_clone: false,
|
|
457
460
|
parent_constraint_schema: null,
|
|
458
461
|
parent_constraint_name: null,
|
|
@@ -531,6 +534,7 @@ describe.concurrent("table.diff", () => {
|
|
|
531
534
|
validated: true,
|
|
532
535
|
is_local: true,
|
|
533
536
|
no_inherit: false,
|
|
537
|
+
is_temporal: false,
|
|
534
538
|
is_partition_clone: false,
|
|
535
539
|
parent_constraint_schema: null,
|
|
536
540
|
parent_constraint_name: null,
|
|
@@ -606,6 +610,104 @@ describe.concurrent("table.diff", () => {
|
|
|
606
610
|
);
|
|
607
611
|
});
|
|
608
612
|
|
|
613
|
+
test("altered temporal constraint metadata triggers drop+add", () => {
|
|
614
|
+
const tMain = new Table({
|
|
615
|
+
...base,
|
|
616
|
+
name: "t_temporal",
|
|
617
|
+
columns: [
|
|
618
|
+
{
|
|
619
|
+
name: "room_id",
|
|
620
|
+
position: 1,
|
|
621
|
+
data_type: "integer",
|
|
622
|
+
data_type_str: "integer",
|
|
623
|
+
is_custom_type: false,
|
|
624
|
+
custom_type_type: null,
|
|
625
|
+
custom_type_category: null,
|
|
626
|
+
custom_type_schema: null,
|
|
627
|
+
custom_type_name: null,
|
|
628
|
+
not_null: false,
|
|
629
|
+
is_identity: false,
|
|
630
|
+
is_identity_always: false,
|
|
631
|
+
is_generated: false,
|
|
632
|
+
collation: null,
|
|
633
|
+
default: null,
|
|
634
|
+
comment: null,
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: "booking_period",
|
|
638
|
+
position: 2,
|
|
639
|
+
data_type: "tstzrange",
|
|
640
|
+
data_type_str: "tstzrange",
|
|
641
|
+
is_custom_type: false,
|
|
642
|
+
custom_type_type: null,
|
|
643
|
+
custom_type_category: null,
|
|
644
|
+
custom_type_schema: null,
|
|
645
|
+
custom_type_name: null,
|
|
646
|
+
not_null: false,
|
|
647
|
+
is_identity: false,
|
|
648
|
+
is_identity_always: false,
|
|
649
|
+
is_generated: false,
|
|
650
|
+
collation: null,
|
|
651
|
+
default: null,
|
|
652
|
+
comment: null,
|
|
653
|
+
},
|
|
654
|
+
],
|
|
655
|
+
constraints: [
|
|
656
|
+
{
|
|
657
|
+
name: "bookings_pkey",
|
|
658
|
+
constraint_type: "p",
|
|
659
|
+
deferrable: false,
|
|
660
|
+
initially_deferred: false,
|
|
661
|
+
validated: true,
|
|
662
|
+
is_local: true,
|
|
663
|
+
no_inherit: false,
|
|
664
|
+
is_temporal: false,
|
|
665
|
+
is_partition_clone: false,
|
|
666
|
+
parent_constraint_schema: null,
|
|
667
|
+
parent_constraint_name: null,
|
|
668
|
+
parent_table_schema: null,
|
|
669
|
+
parent_table_name: null,
|
|
670
|
+
key_columns: ["room_id", "booking_period"],
|
|
671
|
+
foreign_key_columns: null,
|
|
672
|
+
foreign_key_table: null,
|
|
673
|
+
foreign_key_schema: null,
|
|
674
|
+
foreign_key_table_is_partition: null,
|
|
675
|
+
foreign_key_parent_schema: null,
|
|
676
|
+
foreign_key_parent_table: null,
|
|
677
|
+
foreign_key_effective_schema: null,
|
|
678
|
+
foreign_key_effective_table: null,
|
|
679
|
+
on_update: null,
|
|
680
|
+
on_delete: null,
|
|
681
|
+
match_type: null,
|
|
682
|
+
check_expression: null,
|
|
683
|
+
owner: "o1",
|
|
684
|
+
definition: "PRIMARY KEY (room_id, booking_period)",
|
|
685
|
+
},
|
|
686
|
+
],
|
|
687
|
+
});
|
|
688
|
+
const tBranch = new Table({
|
|
689
|
+
...tMain,
|
|
690
|
+
constraints: [
|
|
691
|
+
{
|
|
692
|
+
...tMain.constraints[0],
|
|
693
|
+
is_temporal: true,
|
|
694
|
+
definition: "PRIMARY KEY (room_id, booking_period WITHOUT OVERLAPS)",
|
|
695
|
+
},
|
|
696
|
+
],
|
|
697
|
+
});
|
|
698
|
+
const changes = diffTables(
|
|
699
|
+
testContext,
|
|
700
|
+
{ [tMain.stableId]: tMain },
|
|
701
|
+
{ [tBranch.stableId]: tBranch },
|
|
702
|
+
);
|
|
703
|
+
expect(changes.some((c) => c instanceof AlterTableDropConstraint)).toBe(
|
|
704
|
+
true,
|
|
705
|
+
);
|
|
706
|
+
expect(changes.some((c) => c instanceof AlterTableAddConstraint)).toBe(
|
|
707
|
+
true,
|
|
708
|
+
);
|
|
709
|
+
});
|
|
710
|
+
|
|
609
711
|
test("columns added/dropped/altered (type, default, not null)", () => {
|
|
610
712
|
const main = new Table({ ...base, name: "t2", columns: [] });
|
|
611
713
|
const withCol = new Table({
|
|
@@ -130,6 +130,7 @@ function createAlterConstraintChange(mainTable: Table, branchTable: Table) {
|
|
|
130
130
|
mainC.validated !== branchC.validated ||
|
|
131
131
|
mainC.is_local !== branchC.is_local ||
|
|
132
132
|
mainC.no_inherit !== branchC.no_inherit ||
|
|
133
|
+
mainC.is_temporal !== branchC.is_temporal ||
|
|
133
134
|
JSON.stringify(mainC.key_columns) !==
|
|
134
135
|
JSON.stringify(branchC.key_columns) ||
|
|
135
136
|
JSON.stringify(mainC.foreign_key_columns) !==
|
|
@@ -56,6 +56,7 @@ const tableConstraintPropsSchema = z.object({
|
|
|
56
56
|
validated: z.boolean(),
|
|
57
57
|
is_local: z.boolean(),
|
|
58
58
|
no_inherit: z.boolean(),
|
|
59
|
+
is_temporal: z.boolean(),
|
|
59
60
|
is_partition_clone: z.boolean(),
|
|
60
61
|
parent_constraint_schema: z.string().nullable(),
|
|
61
62
|
parent_constraint_name: z.string().nullable(),
|
|
@@ -284,6 +285,7 @@ select
|
|
|
284
285
|
'validated', c.convalidated,
|
|
285
286
|
'is_local', c.conislocal,
|
|
286
287
|
'no_inherit', c.connoinherit,
|
|
288
|
+
'is_temporal', coalesce((to_jsonb(c)->>'conperiod')::boolean, false),
|
|
287
289
|
|
|
288
290
|
-- NEW: propagated-to-partition tagging (PG15+)
|
|
289
291
|
'is_partition_clone', (c.conparentid <> 0::oid),
|
|
@@ -578,6 +578,7 @@ const pkConstraint = {
|
|
|
578
578
|
validated: true,
|
|
579
579
|
is_local: true,
|
|
580
580
|
no_inherit: false,
|
|
581
|
+
is_temporal: false,
|
|
581
582
|
is_partition_clone: false,
|
|
582
583
|
parent_constraint_schema: null,
|
|
583
584
|
parent_constraint_name: null,
|
|
@@ -609,6 +610,7 @@ const uniqueConstraint = {
|
|
|
609
610
|
validated: true,
|
|
610
611
|
is_local: true,
|
|
611
612
|
no_inherit: false,
|
|
613
|
+
is_temporal: false,
|
|
612
614
|
is_partition_clone: false,
|
|
613
615
|
parent_constraint_schema: null,
|
|
614
616
|
parent_constraint_name: null,
|
|
@@ -639,6 +641,7 @@ const fkConstraint = {
|
|
|
639
641
|
validated: true,
|
|
640
642
|
is_local: true,
|
|
641
643
|
no_inherit: false,
|
|
644
|
+
is_temporal: false,
|
|
642
645
|
is_partition_clone: false,
|
|
643
646
|
parent_constraint_schema: null,
|
|
644
647
|
parent_constraint_name: null,
|
|
@@ -670,6 +673,7 @@ const checkConstraint = {
|
|
|
670
673
|
validated: true,
|
|
671
674
|
is_local: true,
|
|
672
675
|
no_inherit: true,
|
|
676
|
+
is_temporal: false,
|
|
673
677
|
is_partition_clone: false,
|
|
674
678
|
parent_constraint_schema: null,
|
|
675
679
|
parent_constraint_name: null,
|
|
@@ -996,6 +1000,8 @@ const rlsPolicy = new RlsPolicy({
|
|
|
996
1000
|
with_check_expression: null,
|
|
997
1001
|
owner: "owner1",
|
|
998
1002
|
comment: "rls policy comment",
|
|
1003
|
+
referenced_relations: [],
|
|
1004
|
+
referenced_procedures: [],
|
|
999
1005
|
});
|
|
1000
1006
|
|
|
1001
1007
|
const rlsPolicyRestrictive = new RlsPolicy({
|
|
@@ -1009,6 +1015,8 @@ const rlsPolicyRestrictive = new RlsPolicy({
|
|
|
1009
1015
|
with_check_expression: "status <> 'locked'",
|
|
1010
1016
|
owner: "owner1",
|
|
1011
1017
|
comment: null,
|
|
1018
|
+
referenced_relations: [],
|
|
1019
|
+
referenced_procedures: [],
|
|
1012
1020
|
});
|
|
1013
1021
|
|
|
1014
1022
|
const index = new Index({
|
|
@@ -2,12 +2,15 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { Catalog, createEmptyCatalog } from "./catalog.model.ts";
|
|
3
3
|
import type { Change } from "./change.types.ts";
|
|
4
4
|
import {
|
|
5
|
+
AlterTableAddConstraint,
|
|
5
6
|
AlterTableChangeOwner,
|
|
6
7
|
AlterTableDropColumn,
|
|
7
8
|
AlterTableDropConstraint,
|
|
8
9
|
AlterTableEnableRowLevelSecurity,
|
|
9
10
|
AlterTableSetReplicaIdentity,
|
|
11
|
+
AlterTableValidateConstraint,
|
|
10
12
|
} from "./objects/table/changes/table.alter.ts";
|
|
13
|
+
import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.ts";
|
|
11
14
|
import { CreateTable } from "./objects/table/changes/table.create.ts";
|
|
12
15
|
import { DropTable } from "./objects/table/changes/table.drop.ts";
|
|
13
16
|
import { GrantTablePrivileges } from "./objects/table/changes/table.privilege.ts";
|
|
@@ -77,6 +80,7 @@ describe("normalizePostDiffCycles", () => {
|
|
|
77
80
|
validated: true,
|
|
78
81
|
is_local: true,
|
|
79
82
|
no_inherit: false,
|
|
83
|
+
is_temporal: false,
|
|
80
84
|
is_partition_clone: false,
|
|
81
85
|
parent_constraint_schema: null,
|
|
82
86
|
parent_constraint_name: null,
|
|
@@ -117,6 +121,7 @@ describe("normalizePostDiffCycles", () => {
|
|
|
117
121
|
validated: true,
|
|
118
122
|
is_local: true,
|
|
119
123
|
no_inherit: false,
|
|
124
|
+
is_temporal: false,
|
|
120
125
|
is_partition_clone: false,
|
|
121
126
|
parent_constraint_schema: null,
|
|
122
127
|
parent_constraint_name: null,
|
|
@@ -237,6 +242,7 @@ describe("normalizePostDiffCycles", () => {
|
|
|
237
242
|
validated: true,
|
|
238
243
|
is_local: true,
|
|
239
244
|
no_inherit: false,
|
|
245
|
+
is_temporal: false,
|
|
240
246
|
is_partition_clone: false,
|
|
241
247
|
parent_constraint_schema: null,
|
|
242
248
|
parent_constraint_name: null,
|
|
@@ -314,4 +320,140 @@ describe("normalizePostDiffCycles", () => {
|
|
|
314
320
|
expect(normalized).toContain(preExistingReplicaIdentity);
|
|
315
321
|
expect(normalized).toContain(preExistingGrant);
|
|
316
322
|
});
|
|
323
|
+
|
|
324
|
+
test("dedupes duplicate constraint Add/Validate/Comment on replaced tables keeping last occurrence", async () => {
|
|
325
|
+
const baseline = await createEmptyCatalog(170000, "postgres");
|
|
326
|
+
const branchChildren = new Table({
|
|
327
|
+
...baseTableProps,
|
|
328
|
+
name: "children",
|
|
329
|
+
columns: [
|
|
330
|
+
{ ...integerColumn("id", 1), not_null: true },
|
|
331
|
+
integerColumn("parent_ref", 2),
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
const otherTable = new Table({
|
|
335
|
+
...baseTableProps,
|
|
336
|
+
name: "other",
|
|
337
|
+
columns: [{ ...integerColumn("id", 1), not_null: true }],
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const fkConstraint = {
|
|
341
|
+
name: "children_parent_ref_fkey",
|
|
342
|
+
constraint_type: "f" as const,
|
|
343
|
+
deferrable: false,
|
|
344
|
+
initially_deferred: false,
|
|
345
|
+
validated: false,
|
|
346
|
+
is_local: true,
|
|
347
|
+
no_inherit: false,
|
|
348
|
+
is_temporal: true,
|
|
349
|
+
is_partition_clone: false,
|
|
350
|
+
parent_constraint_schema: null,
|
|
351
|
+
parent_constraint_name: null,
|
|
352
|
+
parent_table_schema: null,
|
|
353
|
+
parent_table_name: null,
|
|
354
|
+
key_columns: ["parent_ref"],
|
|
355
|
+
foreign_key_columns: ["id"],
|
|
356
|
+
foreign_key_table: "parents",
|
|
357
|
+
foreign_key_schema: "public",
|
|
358
|
+
foreign_key_table_is_partition: false,
|
|
359
|
+
foreign_key_parent_schema: null,
|
|
360
|
+
foreign_key_parent_table: null,
|
|
361
|
+
foreign_key_effective_schema: "public",
|
|
362
|
+
foreign_key_effective_table: "parents",
|
|
363
|
+
on_update: "a" as const,
|
|
364
|
+
on_delete: "a" as const,
|
|
365
|
+
match_type: "s" as const,
|
|
366
|
+
check_expression: null,
|
|
367
|
+
owner: "postgres",
|
|
368
|
+
definition:
|
|
369
|
+
"FOREIGN KEY (parent_ref, PERIOD valid_period) REFERENCES public.parents(id, PERIOD valid_period)",
|
|
370
|
+
comment: "fk comment",
|
|
371
|
+
};
|
|
372
|
+
const otherConstraint = {
|
|
373
|
+
...fkConstraint,
|
|
374
|
+
name: "other_unique",
|
|
375
|
+
constraint_type: "u" as const,
|
|
376
|
+
foreign_key_table: null,
|
|
377
|
+
foreign_key_schema: null,
|
|
378
|
+
foreign_key_effective_schema: null,
|
|
379
|
+
foreign_key_effective_table: null,
|
|
380
|
+
foreign_key_columns: [],
|
|
381
|
+
key_columns: ["id"],
|
|
382
|
+
definition: "UNIQUE (id)",
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const diffTablesAdd = new AlterTableAddConstraint({
|
|
386
|
+
table: branchChildren,
|
|
387
|
+
constraint: fkConstraint,
|
|
388
|
+
});
|
|
389
|
+
const diffTablesValidate = new AlterTableValidateConstraint({
|
|
390
|
+
table: branchChildren,
|
|
391
|
+
constraint: fkConstraint,
|
|
392
|
+
});
|
|
393
|
+
const diffTablesComment = new CreateCommentOnConstraint({
|
|
394
|
+
table: branchChildren,
|
|
395
|
+
constraint: fkConstraint,
|
|
396
|
+
});
|
|
397
|
+
const expansionAdd = new AlterTableAddConstraint({
|
|
398
|
+
table: branchChildren,
|
|
399
|
+
constraint: fkConstraint,
|
|
400
|
+
});
|
|
401
|
+
const expansionValidate = new AlterTableValidateConstraint({
|
|
402
|
+
table: branchChildren,
|
|
403
|
+
constraint: fkConstraint,
|
|
404
|
+
});
|
|
405
|
+
const expansionComment = new CreateCommentOnConstraint({
|
|
406
|
+
table: branchChildren,
|
|
407
|
+
constraint: fkConstraint,
|
|
408
|
+
});
|
|
409
|
+
const soloOtherTableAdd = new AlterTableAddConstraint({
|
|
410
|
+
table: otherTable,
|
|
411
|
+
constraint: otherConstraint,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const changes: Change[] = [
|
|
415
|
+
new DropTable({ table: branchChildren }),
|
|
416
|
+
new CreateTable({ table: branchChildren }),
|
|
417
|
+
diffTablesAdd,
|
|
418
|
+
diffTablesValidate,
|
|
419
|
+
diffTablesComment,
|
|
420
|
+
soloOtherTableAdd,
|
|
421
|
+
expansionAdd,
|
|
422
|
+
expansionValidate,
|
|
423
|
+
expansionComment,
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
const mainCatalog = new Catalog({
|
|
427
|
+
...baseline,
|
|
428
|
+
tables: { [branchChildren.stableId]: branchChildren },
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const normalized = normalizePostDiffCycles({
|
|
432
|
+
changes,
|
|
433
|
+
mainCatalog,
|
|
434
|
+
replacedTableIds: new Set([branchChildren.stableId]),
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(normalized).not.toContain(diffTablesAdd);
|
|
438
|
+
expect(normalized).not.toContain(diffTablesValidate);
|
|
439
|
+
expect(normalized).not.toContain(diffTablesComment);
|
|
440
|
+
expect(normalized).toContain(expansionAdd);
|
|
441
|
+
expect(normalized).toContain(expansionValidate);
|
|
442
|
+
expect(normalized).toContain(expansionComment);
|
|
443
|
+
expect(normalized).toContain(soloOtherTableAdd);
|
|
444
|
+
|
|
445
|
+
expect(
|
|
446
|
+
normalized.filter((change) => change instanceof AlterTableAddConstraint),
|
|
447
|
+
).toHaveLength(2);
|
|
448
|
+
expect(
|
|
449
|
+
normalized.filter(
|
|
450
|
+
(change) => change instanceof AlterTableValidateConstraint,
|
|
451
|
+
),
|
|
452
|
+
).toHaveLength(1);
|
|
453
|
+
expect(
|
|
454
|
+
normalized.filter(
|
|
455
|
+
(change) => change instanceof CreateCommentOnConstraint,
|
|
456
|
+
),
|
|
457
|
+
).toHaveLength(1);
|
|
458
|
+
});
|
|
317
459
|
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { Catalog } from "./catalog.model.ts";
|
|
2
2
|
import type { Change } from "./change.types.ts";
|
|
3
3
|
import {
|
|
4
|
+
AlterTableAddConstraint,
|
|
4
5
|
AlterTableDropColumn,
|
|
5
6
|
AlterTableDropConstraint,
|
|
7
|
+
AlterTableValidateConstraint,
|
|
6
8
|
} from "./objects/table/changes/table.alter.ts";
|
|
9
|
+
import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.ts";
|
|
7
10
|
import { DropTable } from "./objects/table/changes/table.drop.ts";
|
|
8
11
|
import { stableId } from "./objects/utils.ts";
|
|
9
12
|
|
|
@@ -52,6 +55,72 @@ function isSupersededByTableReplacement(
|
|
|
52
55
|
return replacedTableIds.has(change.table.stableId);
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Drop earlier duplicates of `AlterTableAddConstraint` /
|
|
60
|
+
* `AlterTableValidateConstraint` / `CreateCommentOnConstraint` targeting
|
|
61
|
+
* replaced tables, keeping only the last occurrence of each
|
|
62
|
+
* `(changeType, table.stableId, constraint.name)`.
|
|
63
|
+
*
|
|
64
|
+
* When `expandReplaceDependencies()` promotes a table to a full
|
|
65
|
+
* `DropTable + CreateTable` pair, it also emits one
|
|
66
|
+
* `AlterTableAddConstraint` (plus optional `VALIDATE CONSTRAINT` /
|
|
67
|
+
* `COMMENT ON CONSTRAINT`) per branch constraint. If `diffTables()` already
|
|
68
|
+
* emitted the same change for a shape flip or a new constraint on that
|
|
69
|
+
* table, the plan ends up with two identical `ALTER TABLE ... ADD
|
|
70
|
+
* CONSTRAINT ...` statements and PostgreSQL fails at apply time with
|
|
71
|
+
* `constraint "..." for relation "..." already exists`. Because
|
|
72
|
+
* `expandReplaceDependencies()` appends its additions after the original
|
|
73
|
+
* `diffTables()` output, the last occurrence is the expansion's emission —
|
|
74
|
+
* keeping it preserves correctness while removing the duplicate.
|
|
75
|
+
*/
|
|
76
|
+
function dropReplacedTableDuplicateConstraintChanges(
|
|
77
|
+
changes: Change[],
|
|
78
|
+
replacedTableIds: ReadonlySet<string>,
|
|
79
|
+
): Change[] {
|
|
80
|
+
if (replacedTableIds.size === 0) return changes;
|
|
81
|
+
|
|
82
|
+
const keyFor = (change: Change): string | null => {
|
|
83
|
+
if (
|
|
84
|
+
!(change instanceof AlterTableAddConstraint) &&
|
|
85
|
+
!(change instanceof AlterTableValidateConstraint) &&
|
|
86
|
+
!(change instanceof CreateCommentOnConstraint)
|
|
87
|
+
) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (!replacedTableIds.has(change.table.stableId)) return null;
|
|
91
|
+
const tag =
|
|
92
|
+
change instanceof AlterTableAddConstraint
|
|
93
|
+
? "add"
|
|
94
|
+
: change instanceof AlterTableValidateConstraint
|
|
95
|
+
? "validate"
|
|
96
|
+
: "comment";
|
|
97
|
+
return `${tag}:${constraintStableId(change.table, change.constraint.name)}`;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const seen = new Set<string>();
|
|
101
|
+
const reversedKept: Change[] = [];
|
|
102
|
+
let mutated = false;
|
|
103
|
+
|
|
104
|
+
// Walk backwards: the first encounter of each key corresponds to its LAST
|
|
105
|
+
// occurrence in the original order. `expandReplaceDependencies()` appends
|
|
106
|
+
// additions after the original changes, so "last wins" keeps the
|
|
107
|
+
// expansion's emission and drops the earlier diffTables duplicate.
|
|
108
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
109
|
+
const change = changes[i] as Change;
|
|
110
|
+
const key = keyFor(change);
|
|
111
|
+
if (key !== null) {
|
|
112
|
+
if (seen.has(key)) {
|
|
113
|
+
mutated = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
seen.add(key);
|
|
117
|
+
}
|
|
118
|
+
reversedKept.push(change);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return mutated ? reversedKept.reverse() : changes;
|
|
122
|
+
}
|
|
123
|
+
|
|
55
124
|
function collectExplicitConstraintDropIds(changes: Change[]) {
|
|
56
125
|
const explicitConstraintDropIds = new Set<string>();
|
|
57
126
|
|
|
@@ -84,6 +153,13 @@ function hasSameEntries(
|
|
|
84
153
|
* - If replace expansion added `DropTable(T)+CreateTable(T)`, targeted
|
|
85
154
|
* `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)` changes are
|
|
86
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.
|
|
87
163
|
* - If two dropped tables reference each other via FK, we insert dedicated
|
|
88
164
|
* `AlterTableDropConstraint` changes and teach the paired `DropTable`
|
|
89
165
|
* changes not to claim those FK stable IDs.
|
|
@@ -100,10 +176,15 @@ export function normalizePostDiffCycles({
|
|
|
100
176
|
mainCatalog: Catalog;
|
|
101
177
|
replacedTableIds?: ReadonlySet<string>;
|
|
102
178
|
}): Change[] {
|
|
179
|
+
const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(
|
|
180
|
+
changes,
|
|
181
|
+
replacedTableIds,
|
|
182
|
+
);
|
|
183
|
+
|
|
103
184
|
const structurallyNormalizedChanges =
|
|
104
185
|
replacedTableIds.size === 0
|
|
105
|
-
?
|
|
106
|
-
:
|
|
186
|
+
? dedupedChanges
|
|
187
|
+
: dedupedChanges.filter(
|
|
107
188
|
(change) => !isSupersededByTableReplacement(change, replacedTableIds),
|
|
108
189
|
);
|
|
109
190
|
|