@supabase/pg-delta 1.0.0-alpha.16 → 1.0.0-alpha.18

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.
@@ -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
- ? changes
106
- : changes.filter(
186
+ ? dedupedChanges
187
+ : dedupedChanges.filter(
107
188
  (change) => !isSupersededByTableReplacement(change, replacedTableIds),
108
189
  );
109
190