@@ -63,28 +63,36 @@ new migration from the metadata-vs-DB diff before applying it. `apply-pending` j
6363the pending already-committed files, making it idempotent; ` --dry-run ` lists what would
6464run. postgres/sqlite only — on D1 use ` wrangler d1 migrations apply ` .
6565
66- #### D1 limitation : rebuilding a foreign-key-referenced table
66+ #### D1: rebuilding a foreign-key-referenced table
6767
6868Cloudflare D1 applies each migration inside its own implicit transaction, and SQLite
6969ignores ` PRAGMA foreign_keys ` while a transaction is open. The SQLite table-rebuild
7070recipe (used for a ` CHECK ` , column type/nullability/default, foreign-key, or
7171` field.enum ` values change) relies on ` PRAGMA foreign_keys = OFF ` taking effect before
7272it drops and recreates the table — which does not happen on remote D1.
7373
74- To avoid emitting a migration that would fail against a populated production database
75- with ` FOREIGN KEY constraint failed ` , ` meta migrate --dialect d1 ` ** refuses at
76- generation time** when a change would rebuild a table that another table's foreign key
77- references. Apply such a change by hand-writing the migration: rebuild the referencing
78- table to temporarily drop its foreign key, rebuild the referenced table, then restore
79- the foreign key — or make the change on an unreferenced table. (Auto-generating this
80- cascade is tracked as a follow-up, [ #241 ] ( https://github.com/metaobjectsdev/metaobjects/issues/241 ) .)
81-
82- ** Known limitation of the current refusal:** it is computed from the * target* schema's
83- foreign keys. A single migration that ** both** rebuilds a referenced table ** and** drops
84- the referencing foreign key in the same run is therefore not detected, and can still fail
85- on remote D1 (the parent's ` DROP TABLE ` can be emitted before the referencing table is
86- rebuilt). Split such a change into two migrations, or hand-write it. Closing this gap
87- requires the actual-schema foreign-key graph and rides the same follow-up (#241 ).
74+ ` meta migrate --dialect d1 ` handles this by ** auto-generating a cascade**
75+ ([ #241 ] ( https://github.com/metaobjectsdev/metaobjects/issues/241 ) , closing
76+ [ #226 ] ( https://github.com/metaobjectsdev/metaobjects/issues/226 ) 's residual
77+ under-refuse gap below) instead of refusing outright. When a change would rebuild a
78+ table that another table's foreign key references, the emitter rebuilds that table
79+ together with every table that transitively references it, in one pass: the affected
80+ tables are dropped referrers-first and recreated parents-first, under `PRAGMA
81+ defer_foreign_keys = ON` so every foreign-key check defers to the end of D1's implicit
82+ transaction instead of firing mid-rebuild. The result applies cleanly against a
83+ populated production database and re-converges — a follow-up ` meta verify ` /`meta
84+ migrate` sees no drift. The cascade is built over the ** union of the actual (live) and
85+ expected (target) schemas' foreign-key graphs** , so it also covers the case a
86+ target-schema-only check would miss: a single migration that both rebuilds a
87+ referenced table * and* drops the referencing foreign key in the same run.
88+
89+ The one case still hand-written: a ** multi-table foreign-key cycle** (table A
90+ references B references … references A, two or more tables). A cycle has no
91+ parents-first rebuild order, so ` meta migrate --dialect d1 ` still ** refuses at
92+ generation time** — hand-write the migration (drop the foreign key on one side of the
93+ cycle, rebuild the tables, then restore it) or break the cycle in your metadata. A
94+ self-referencing table (a table whose own foreign key targets itself) is not a cycle
95+ in this sense and is handled by the cascade like any other rebuild.
8896
8997### Java
9098
0 commit comments