You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(pg-delta): preserve `REPLICA IDENTITY USING INDEX` on tables instead of silently reverting to `DEFAULT` on declarative sync.
6
+
7
+
The table extractor only stored `replica_identity` as a single character (`'d' | 'n' | 'f' | 'i'`) and discarded the index name when the mode was `'i'`. The diff path then explicitly skipped mode `'i'` ("handled by index changes" — but no such handler existed), and `AlterTableSetReplicaIdentity.serialize()` fell back to `REPLICA IDENTITY DEFAULT` for that mode. Compounding this, `Index.is_replica_identity` participated in equality and was marked non-alterable, so toggling the flag on the index triggered a spurious `DROP INDEX` + `CREATE INDEX` — and Postgres reverts the table to `REPLICA IDENTITY DEFAULT` whenever the configured replica-identity index is dropped.
8
+
9
+
End result: a table configured with `ALTER TABLE foo REPLICA IDENTITY USING INDEX foo_idx` would extract as `replica_identity = 'i'` but produce no setter on diff. The next `declarative sync` would generate a migration that dropped the user's index, reset the table to `DEFAULT`, and recreated the index — never converging (reported as supabase/cli#5141).
10
+
11
+
The fix:
12
+
13
+
-`Table.replica_identity_index` is extracted via `pg_index.indisreplident` and included in `dataFields`, so the index name participates in equality.
14
+
-`AlterTableSetReplicaIdentity` now serializes `REPLICA IDENTITY USING INDEX <name>` for mode `'i'` and declares the index as a `requires` dependency so it is created first.
15
+
- The table diff emits the change for all modes (including `'i'`) on both `CREATE` and `ALTER`, and re-emits when the configured index name changes while staying in `'i'` mode.
16
+
-`Index.is_replica_identity` is no longer in `dataFields` / `NON_ALTERABLE_FIELDS`; the table side is the source of truth, set via `ALTER TABLE`. This stops the spurious `DROP INDEX` + `CREATE INDEX` cycle.
17
+
- A new `restoreReplicaIdentityAfterIndexReplace` pass in `post-diff-normalization.ts` re-emits `ALTER TABLE ... REPLICA IDENTITY USING INDEX <name>` after any `DropIndex(idx) + CreateIndex(idx)` pair where `idx` is the replica-identity index of a branch table. This covers the second flavor of the bug: when both main and branch already point at the same replica-identity index, but that index's *definition* changes (e.g. a column added to its key), the index is replaced, Postgres silently flips `relreplident` to `'d'`, and the table-level diff alone cannot see the cross-object interaction. The pass is idempotent — if `diffTables()` already emitted the same setter (because the table is also flipping mode or pointing to a different index), no duplicate is added.
18
+
19
+
The post-diff layer file `src/core/post-diff-cycle-breaking.ts` is renamed to `post-diff-normalization.ts` and `normalizePostDiffCycles` to `normalizePostDiffChanges` — the file already contained dedup and replacement-superseded pruning that aren't strictly cycle-breaking, and actual cycle breaking moved to the lazy sort-phase dispatcher in a previous release. The rename brings the file in line with the "post-diff normalization" terminology already used in the package's `CLAUDE.md` rule of thumb.
Copy file name to clipboardExpand all lines: packages/pg-delta/CLAUDE.md
+15-1Lines changed: 15 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,7 +88,7 @@ for (const pgVersion of POSTGRES_VERSIONS) {
88
88
Keep cycle handling split by the scope of information it needs:
89
89
90
90
-**Object-local PostgreSQL semantics stay in `diff*`**. If a single object diff can prove a statement is redundant or invalid on its own, fix it there. Example: `src/core/objects/sequence/sequence.diff.ts` skips `DROP SEQUENCE` when `OWNED BY` means PostgreSQL will already cascade-drop it with the owning table/column.
91
-
-**Whole-plan interactions belong in post-diff normalization**. If the fix only becomes obvious after multiple emitted changes are combined, implement it in `src/core/post-diff-cycle-breaking.ts`, wired from `src/core/catalog.diff.ts` after raw diffs and `expandReplaceDependencies()`. Current examples: mutual dropped-table FK cycles and pruning same-table `AlterTableDropColumn` / `AlterTableDropConstraint` changes that are superseded by an expansion-added `DropTable+CreateTable` pair.
91
+
-**Whole-plan interactions belong in post-diff normalization**. If the fix only becomes obvious after multiple emitted changes are combined, implement it in `src/core/post-diff-normalization.ts` (`normalizePostDiffChanges`), wired from `src/core/catalog.diff.ts` after raw diffs and `expandReplaceDependencies()`. The pass is the single chokepoint that observes the final `Change[]`, so it catches cross-object effects regardless of whether the relevant change pair was emitted by an object's `diff*` (e.g. `index.diff` for a definition-changed index) or by `expandReplaceDependencies()` (dependency-closure replacement). Current examples: pruning same-table `AlterTableDropColumn` / `AlterTableDropConstraint` changes superseded by an expansion-added `DropTable+CreateTable` pair, deduplicating constraint Add/Validate/Comment on replaced tables, and re-emitting `ALTER TABLE … REPLICA IDENTITY USING INDEX` after a replica-identity index is dropped+recreated.
92
92
-**`expandReplaceDependencies()` only computes replacement closure**. It may report metadata such as which tables were promoted to replacement pairs, but it should not own unrelated cycle-pruning policy.
93
93
-**`src/core/sort/dependency-filter.ts` is a narrow last resort**. Use it only for safe edge filtering where the emitted statements are already valid and only the graph edge is artificial. Do not extend sort-phase filtering to paper over plans that would still fail at apply time.
94
94
@@ -108,6 +108,20 @@ Used to track objects across databases (OIDs differ per environment):
0 commit comments