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
PostgreSQL tooling for comparing schemas, planning migrations, and ordering DDL safely. This context captures the project language used when discussing pg-delta migration planning and dependency-cycle handling.
4
+
5
+
## Language
6
+
7
+
**Migration plan**:
8
+
An ordered set of DDL changes that transforms a source database schema into a target database schema.
9
+
_Avoid_: Script, diff output
10
+
11
+
**Change**:
12
+
A single schema operation emitted by a diff, carrying the stable identifiers it creates, drops, or requires.
13
+
_Avoid_: Statement, when referring to the typed operation before serialization
14
+
15
+
**Stable identifier**:
16
+
An environment-independent name for a schema object used to connect changes to catalog dependencies.
17
+
_Avoid_: OID
18
+
19
+
**Dependency cycle**:
20
+
A cycle in the migration-plan dependency graph that prevents topological ordering.
21
+
_Avoid_: Circular diff
22
+
23
+
**Structural normalization**:
24
+
A deterministic rewrite of the final change list before dependency sorting.
25
+
_Avoid_: Cycle breaker
26
+
27
+
**Cycle-breaking change injection**:
28
+
A sort-phase rewrite that injects or rebuilds changes after an unbreakable dependency cycle is detected.
29
+
_Avoid_: Post-diff normalization, when the fix is specific to a detected graph cycle
30
+
31
+
**Publication FK-chain constraint-drop cycle**:
32
+
A dependency cycle where publication membership is being removed for dropped tables, those dropped tables carry a foreign-key chain, and the chain ends at a separately dropped referenced constraint.
33
+
_Avoid_: Publication drop cycle, dropped-table publication membership cycle
34
+
35
+
**FK constraint-drop injection**:
36
+
Cycle-breaking change injection that creates explicit foreign-key constraint drops and makes table drops stop claiming those constraint stable identifiers.
37
+
_Avoid_: Relaxed publication requirement, when resolving dropped-table publication membership cycles
38
+
39
+
## Relationships
40
+
41
+
- A **Migration plan** contains one or more **Changes**.
42
+
- A **Change** names the **Stable identifiers** it creates, drops, or requires.
43
+
-**Structural normalization** happens before dependency sorting and does not inspect a specific cycle path.
44
+
-**Cycle-breaking change injection** happens during dependency sorting and responds to a concrete **Dependency cycle**.
45
+
- A **Publication FK-chain constraint-drop cycle** is resolved by **Cycle-breaking change injection**, not by structural normalization.
46
+
- A **Publication FK-chain constraint-drop cycle** is resolved with **FK constraint-drop injection** while leaving publication membership and referenced-constraint drop changes unchanged.
47
+
- In a **Publication FK-chain constraint-drop cycle**, the terminal referenced-constraint drop table must be part of the publication membership being removed.
48
+
-**FK constraint-drop injection** for a **Publication FK-chain constraint-drop cycle** is cycle-local: inject only FK drops that point to a dropped table in the cycle or to the terminal referenced constraint being dropped.
49
+
-**FK constraint-drop injection** can be shared by multiple cycle breakers; each breaker still owns its own matcher and safety checks.
50
+
51
+
## Example dialogue
52
+
53
+
> **Dev:** "Should this publication/table drop issue be handled by structural normalization?"
54
+
> **Domain expert:** "No. The final change list is valid; the problem appears only after dependency sorting detects the specific cycle, so it belongs in cycle-breaking change injection."
55
+
56
+
## Flagged ambiguities
57
+
58
+
- "Whole-plan interaction" was used for both **Structural normalization** and **Cycle-breaking change injection**. Resolved: deterministic rewrites of the final change list are structural normalization; rewrites triggered by a concrete unbreakable dependency cycle are cycle-breaking change injection.
59
+
-`AlterTableDropConstraint` was first described as optional in the publication/table drop cycle. Resolved: the observed production cycle is a **Publication FK-chain constraint-drop cycle**, so a separately emitted referenced-constraint drop is part of that specific matcher.
60
+
- Rebuilding `AlterPublicationDropTables` with relaxed requirements was considered for **Publication FK-chain constraint-drop cycles**. Resolved: keep publication membership changes unchanged and break the foreign-key chain with **FK constraint-drop injection**.
Copy file name to clipboardExpand all lines: packages/pg-delta/CLAUDE.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,11 +88,12 @@ 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-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.
91
+
-**Deterministic whole-plan rewrites belong in post-diff normalization**. If the final `Change[]` itself should be rewritten before dependency sorting, 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
+
-**Unbreakable graph cycles belong in sort-phase change injection**. If the emitted statements are valid but topological sorting discovers a hard dependency cycle that cannot be solved by weak-edge filtering, implement the narrow pattern in `src/core/sort/cycle-breakers.ts` (`tryBreakCycleByChangeInjection`). Existing examples: injecting explicit FK constraint drops for dropped-table FK cycles and rebuilding `AlterTableDropColumn` for publication-column cycles on surviving tables.
92
93
-**`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
94
-**`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
95
95
-
Rule of thumb: if the fix needs the full final `Change[]`, it is post-diff; if it needs only one object's semantics, it belongs in that object's `diff*`; if it only removes a graph edge without changing emitted SQL, it belongs in the sort filter.
96
+
Rule of thumb: if the fix changes a valid final `Change[]` before graph construction, it is post-diff; if it reacts to a concrete unbreakable dependency cycle and needs to inject or rebuild changes, it belongs in the sort-phase cycle breakers; if it needs only one object's semantics, it belongs in that object's `diff*`; if it only removes a graph edge without changing emitted SQL, it belongs in the sort filter.
0 commit comments