meta migrate will write a bare DROP TABLE "x" into a committed migration when x exists in the live database and is absent from the metadata — even when no migration in the chain ever creates x. In our case another tool (drizzle-kit) owned that table.
Replaying the chain against an empty database then dies:
$ meta migrate apply-pending --db postgresql://…/fresh
meta: migrate apply-pending: apply failed: table "arena_season_standing" does not exist
Nothing warns at generation time, so the chain looks healthy until someone actually tries to provision a fresh database. Ours was broken for roughly three months before anyone noticed; the only working development database left on the machine was a leftover container from a CI run.
This directly contradicts the documented contract for apply-pending, from meta migrate --help:
apply-pending Replay committed migration files against --db (no diff); provisions a fresh/CI database. postgres/sqlite only.
Two asks
-
Emit DROP TABLE IF EXISTS / DROP VIEW IF EXISTS for objects the chain does not itself create. Postgres and SQLite both support it, and it costs nothing when the object is present. The drop is a legitimate convergence step for the database it was generated against; it just must not become a landmine for every future replay.
-
Let meta verify assert that the committed chain replays from empty. The machinery already exists — apply-pending against a scratch database, then --from-db --dry-run and check for no schema changes. As a gate it turns a silent months-long rot into a failing build on the commit that introduces it. We ended up writing exactly this as a project-level integration test; it feels like it belongs in the tool.
Notes
meta migratewill write a bareDROP TABLE "x"into a committed migration whenxexists in the live database and is absent from the metadata — even when no migration in the chain ever createsx. In our case another tool (drizzle-kit) owned that table.Replaying the chain against an empty database then dies:
Nothing warns at generation time, so the chain looks healthy until someone actually tries to provision a fresh database. Ours was broken for roughly three months before anyone noticed; the only working development database left on the machine was a leftover container from a CI run.
This directly contradicts the documented contract for
apply-pending, frommeta migrate --help:Two asks
Emit
DROP TABLE IF EXISTS/DROP VIEW IF EXISTSfor objects the chain does not itself create. Postgres and SQLite both support it, and it costs nothing when the object is present. The drop is a legitimate convergence step for the database it was generated against; it just must not become a landmine for every future replay.Let
meta verifyassert that the committed chain replays from empty. The machinery already exists —apply-pendingagainst a scratch database, then--from-db --dry-runand check forno schema changes. As a gate it turns a silent months-long rot into a failing build on the commit that introduces it. We ended up writing exactly this as a project-level integration test; it feels like it belongs in the tool.Notes
@metaobjectsdev/cli0.23.1, dialect postgres (server 16.14 and 18.6).dialecttodiff()— every Postgres view reports drift forever, and CHECK constraints are never diffed #297 (verify --dbfalse-positives every view), in that both make drift gates hard to trust: one gate cannot fail, and the other cannot be believed.IF EXISTSshape is acceptable.