@supabase/pg-delta 1.0.0-alpha.28 → 1.0.0-alpha.29
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.
|
@@ -351,9 +351,14 @@ function tryBreakPublicationFkConstraintDropCycle(cycleNodeIndexes, phaseChanges
|
|
|
351
351
|
if (!publicationTableIds.has(terminalConstraintDrop.table.stableId)) {
|
|
352
352
|
return null;
|
|
353
353
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
// At least one dropped table must be a publication member — that's the
|
|
355
|
+
// publication → DropTable edge that pulls the publication change into the
|
|
356
|
+
// cycle (the back-edge is the terminal constraint's table, checked above).
|
|
357
|
+
// Don't require ALL of them: publications like supabase_realtime commonly
|
|
358
|
+
// contain only a subset of tables, so intermediate FK-chain tables may not
|
|
359
|
+
// be members (Sentry SUPABASE-API-7RS / CLI-1605).
|
|
360
|
+
if (!dropTables.some((dropTable) => publicationTableIds.has(dropTable.table.stableId))) {
|
|
361
|
+
return null;
|
|
357
362
|
}
|
|
358
363
|
const cycleDropTableIds = new Set(dropTables.map((change) => change.table.stableId));
|
|
359
364
|
let hasFkToTerminalConstraint = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/pg-delta",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.29",
|
|
4
4
|
"description": "PostgreSQL migrations made easy",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"diff",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@stricli/core": "^1.2.4",
|
|
82
|
-
"@supabase/pg-topo": "^1.0.0-alpha.
|
|
82
|
+
"@supabase/pg-topo": "^1.0.0-alpha.2",
|
|
83
83
|
"@ts-safeql/sql-tag": "^0.2.0",
|
|
84
84
|
"chalk": "^5.6.2",
|
|
85
85
|
"debug": "^4.3.7",
|
|
@@ -682,6 +682,132 @@ describe("tryBreakCycleByChangeInjection", () => {
|
|
|
682
682
|
expect(broken).toContain(terminalDrop);
|
|
683
683
|
});
|
|
684
684
|
|
|
685
|
+
test("publication FK-chain 4-cycle with partial publication membership: injects FK drops", () => {
|
|
686
|
+
// Sentry SUPABASE-API-7RS / CLI-1605. Same shape as the previous test,
|
|
687
|
+
// but the publication only contains the terminal constraint's table
|
|
688
|
+
// (trades) and the first dropped table (public_offering_events) — the
|
|
689
|
+
// intermediate FK-chain table (trade_status_events) was never a member
|
|
690
|
+
// of supabase_realtime. The breaker must not require every dropped
|
|
691
|
+
// table in the cycle to be a publication member; the pub edge only
|
|
692
|
+
// needs one of them.
|
|
693
|
+
//
|
|
694
|
+
// Schema:
|
|
695
|
+
// trades.trade_id UNIQUE (trades_trade_id_key) — table survives
|
|
696
|
+
// trade_status_events.trade_id REFERENCES trades(trade_id)
|
|
697
|
+
// public_offering_events.source_event_id REFERENCES trade_status_events(id)
|
|
698
|
+
// publication supabase_realtime: trades, public_offering_events only
|
|
699
|
+
const tableTrades = new Table({
|
|
700
|
+
...baseTableProps,
|
|
701
|
+
name: "trades",
|
|
702
|
+
columns: [
|
|
703
|
+
{ ...integerColumn("id", 1), not_null: true },
|
|
704
|
+
{ ...integerColumn("trade_id", 2), not_null: true },
|
|
705
|
+
],
|
|
706
|
+
constraints: [uniqueConstraint("trades_trade_id_key", "trade_id")],
|
|
707
|
+
});
|
|
708
|
+
const tableTradeStatusEvents = new Table({
|
|
709
|
+
...baseTableProps,
|
|
710
|
+
name: "trade_status_events",
|
|
711
|
+
columns: [
|
|
712
|
+
{ ...integerColumn("id", 1), not_null: true },
|
|
713
|
+
integerColumn("trade_id", 2),
|
|
714
|
+
],
|
|
715
|
+
constraints: [
|
|
716
|
+
fkConstraint({
|
|
717
|
+
name: "trade_status_events_trade_id_fkey",
|
|
718
|
+
fkColumn: "trade_id",
|
|
719
|
+
targetSchema: "public",
|
|
720
|
+
targetTable: "trades",
|
|
721
|
+
targetColumn: "trade_id",
|
|
722
|
+
}),
|
|
723
|
+
],
|
|
724
|
+
});
|
|
725
|
+
const tablePublicOfferingEvents = new Table({
|
|
726
|
+
...baseTableProps,
|
|
727
|
+
name: "public_offering_events",
|
|
728
|
+
columns: [
|
|
729
|
+
{ ...integerColumn("id", 1), not_null: true },
|
|
730
|
+
integerColumn("source_event_id", 2),
|
|
731
|
+
],
|
|
732
|
+
constraints: [
|
|
733
|
+
fkConstraint({
|
|
734
|
+
name: "public_offering_events_source_event_id_fkey",
|
|
735
|
+
fkColumn: "source_event_id",
|
|
736
|
+
targetSchema: "public",
|
|
737
|
+
targetTable: "trade_status_events",
|
|
738
|
+
}),
|
|
739
|
+
],
|
|
740
|
+
});
|
|
741
|
+
const publication = new Publication({
|
|
742
|
+
name: "supabase_realtime",
|
|
743
|
+
owner: "postgres",
|
|
744
|
+
comment: null,
|
|
745
|
+
all_tables: false,
|
|
746
|
+
publish_insert: true,
|
|
747
|
+
publish_update: true,
|
|
748
|
+
publish_delete: true,
|
|
749
|
+
publish_truncate: true,
|
|
750
|
+
publish_via_partition_root: false,
|
|
751
|
+
tables: [
|
|
752
|
+
{
|
|
753
|
+
schema: "public",
|
|
754
|
+
name: "public_offering_events",
|
|
755
|
+
columns: null,
|
|
756
|
+
row_filter: null,
|
|
757
|
+
},
|
|
758
|
+
{ schema: "public", name: "trades", columns: null, row_filter: null },
|
|
759
|
+
],
|
|
760
|
+
schemas: [],
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
const terminalDrop = new AlterTableDropConstraint({
|
|
764
|
+
table: tableTrades,
|
|
765
|
+
constraint: tableTrades.constraints[0],
|
|
766
|
+
});
|
|
767
|
+
const changes: Change[] = [
|
|
768
|
+
new AlterPublicationDropTables({
|
|
769
|
+
publication,
|
|
770
|
+
tables: publication.tables,
|
|
771
|
+
}),
|
|
772
|
+
new DropTable({ table: tablePublicOfferingEvents }),
|
|
773
|
+
new DropTable({ table: tableTradeStatusEvents }),
|
|
774
|
+
terminalDrop,
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
const broken = tryBreakCycleByChangeInjection([0, 1, 2, 3], changes);
|
|
778
|
+
if (broken === null) throw new Error("expected breaker to fire");
|
|
779
|
+
|
|
780
|
+
const injectedDropNames = broken
|
|
781
|
+
.filter(
|
|
782
|
+
(change): change is AlterTableDropConstraint =>
|
|
783
|
+
change instanceof AlterTableDropConstraint && change !== terminalDrop,
|
|
784
|
+
)
|
|
785
|
+
.map((change) => change.constraint.name)
|
|
786
|
+
.sort();
|
|
787
|
+
expect(injectedDropNames).toEqual([
|
|
788
|
+
"public_offering_events_source_event_id_fkey",
|
|
789
|
+
"trade_status_events_trade_id_fkey",
|
|
790
|
+
]);
|
|
791
|
+
|
|
792
|
+
for (const [tableId, constraintName] of [
|
|
793
|
+
[
|
|
794
|
+
tablePublicOfferingEvents.stableId,
|
|
795
|
+
"public_offering_events_source_event_id_fkey",
|
|
796
|
+
],
|
|
797
|
+
[tableTradeStatusEvents.stableId, "trade_status_events_trade_id_fkey"],
|
|
798
|
+
] as const) {
|
|
799
|
+
const rewrittenDrop = broken.find(
|
|
800
|
+
(change): change is DropTable =>
|
|
801
|
+
change instanceof DropTable && change.table.stableId === tableId,
|
|
802
|
+
);
|
|
803
|
+
if (!rewrittenDrop) throw new Error(`missing DropTable for ${tableId}`);
|
|
804
|
+
expect(
|
|
805
|
+
rewrittenDrop.externallyDroppedConstraints.has(constraintName),
|
|
806
|
+
).toBe(true);
|
|
807
|
+
}
|
|
808
|
+
expect(broken).toContain(terminalDrop);
|
|
809
|
+
});
|
|
810
|
+
|
|
685
811
|
test("returns null for a cycle with no recognised pattern (e.g. publication-only)", () => {
|
|
686
812
|
// Cycle of `AlterPublicationSetOwner` changes — neither FK nor
|
|
687
813
|
// publication-column shape. Breaker must bail so the formatted
|
|
@@ -418,8 +418,18 @@ function tryBreakPublicationFkConstraintDropCycle(
|
|
|
418
418
|
return null;
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
// At least one dropped table must be a publication member — that's the
|
|
422
|
+
// publication → DropTable edge that pulls the publication change into the
|
|
423
|
+
// cycle (the back-edge is the terminal constraint's table, checked above).
|
|
424
|
+
// Don't require ALL of them: publications like supabase_realtime commonly
|
|
425
|
+
// contain only a subset of tables, so intermediate FK-chain tables may not
|
|
426
|
+
// be members (Sentry SUPABASE-API-7RS / CLI-1605).
|
|
427
|
+
if (
|
|
428
|
+
!dropTables.some((dropTable) =>
|
|
429
|
+
publicationTableIds.has(dropTable.table.stableId),
|
|
430
|
+
)
|
|
431
|
+
) {
|
|
432
|
+
return null;
|
|
423
433
|
}
|
|
424
434
|
|
|
425
435
|
const cycleDropTableIds = new Set(
|