navree
Documentation

Running migrations

The migration commands: migrate, migrate:status, --pretend, rolling back by batch or step, reset, refresh and fresh, the production guard, migrating on boot, and the migrator's API for code and tests.

On this page

Migrations run through commands that DatabaseModule registers on your app. The navree CLI forwards them to your app's binary (navree migrate runs cargo run --quiet -- migrate in the project); in a deployment, run the built binary directly (./crm-api migrate). Every command boots the app first, so it reads the same configuration and connects to the same database as the app does.

CommandWhat it does
migrateRuns every pending migration, as one new batch.
migrate --stepRuns them as one batch each, so they roll back one at a time.
migrate --pretendPrints the SQL the pending migrations would run, and runs nothing.
migrate --seedRuns the seeders afterwards.
migrate --schema-path <path>Loads the schema dump from another file.
migrate:statusEvery migration in run order, with its batch and whether it ran. --pending lists only those that haven't; --json prints module, name, applied, batch and applied_at for each.
migrate:rollbackReverts the last batch, newest migration first.
migrate:rollback --step 3Reverts the last three migrations, whatever their batches.
migrate:rollback --batch 2Reverts batch 2.
migrate:resetReverts every migration, newest first.
migrate:refreshReverts every migration (or the last --step N), then migrates again (--seed to seed).
migrate:freshDrops every table, then migrates (--seed, --step, --drop-views, --drop-types, --schema-path).
migrate:installCreates the migrations table.
schema:dumpWrites the schema to database/schema/<driver>-schema.sql (squashing migrations).

Options take a value after a space or an = (--step 3, --batch=2). An unknown option, a missing value, or --step together with --batch stops the command before it changes anything, with a message saying what is wrong; for an unknown option, it lists the options the command takes.

Migrating

Shell
Text

A notice says what the database couldn't do as asked; the migration still ran. migrate prints "Nothing to migrate." when everything has run. Each migrate run that applies something is one batch, which migrate:rollback reverts together.

migrate:status shows where the database stands:

Text

Pretending

--pretend (on migrate, migrate:rollback and migrate:reset) prints each migration's SQL and changes nothing, not even the migrations table:

Text

Schema builder migrations read the real database while pretending, so a SQLite table rebuild or a hypertable conversion is planned against the tables as they are. A migration that changes a table an earlier pending migration creates can't be planned until that one has run, and says so. SeaORM migrations can't read while pretending; their statements are printed as written.

Use it before deploying a migration to a database you care about: it shows exactly what will run there, for that database's dialect and version.

Rolling back

migrate:rollback calls down on every migration of the last batch, newest first, and deletes their rows; --step N reverts the last N migrations across batches, and --batch N one batch. If the migrations to revert include one that no module registers any more (you deleted the file), the rollback reverts the others, then fails with an error naming it.

migrate:reset reverts everything. migrate:refresh reverts everything (or the last --step N) and migrates again, which checks that every down undoes its up: run it before you push a new migration.

Starting over

migrate:fresh drops every table in the database, including tables no migration created, then runs every migration again as batch 1:

  • SQLite: every table, on one connection with foreign-key checks off.
  • PostgreSQL: every table of the current schema, with CASCADE.
  • MySQL and MariaDB: every base table of the current database in one DROP TABLE, on one connection with FOREIGN_KEY_CHECKS = 0, turned back on before that connection returns to the pool.

--drop-views also drops the views, and --drop-types PostgreSQL's types and domains (an error on other databases, checked before anything is dropped). It is the quickest way back to a clean development database: navree migrate:fresh --seed.

In production

migrate:rollback, migrate:reset, migrate:refresh and migrate:fresh change or drop tables, so they refuse to run when the configuration's env is production (APP_ENV=production, or app.env), unless you pass --force:

Text

migrate itself runs as usual in production: it is the release step of a deployment. It also accepts --force, so a deploy script that passes it works. Run it once per release, before the new version takes traffic, so a failing migration stops the deploy instead of the app (deployment).

Migrating on boot

With database.migrate_on_boot = true, pending migrations run while the app boots, before any provider that uses the database is built. Test apps turn it on for their temporary databases, and a new app's config/app.toml turns it on for development. That file applies in every environment, so in production turn it off with APP_DATABASE__MIGRATE_ON_BOOT=false and run migrate as a release step.

Every command boots the app, so with migrate_on_boot on, migrate:rollback would first apply anything pending. Turn it off for the rollback:

Shell

The migrations table

Navree records applied migrations in navree_migrations (the MIGRATIONS_TABLE constant), created on first use or by migrate:install:

ColumnHolds
moduleThe set's name, e.g. deals.
nameThe migration's name.
batchThe batch it ran in.
applied_atWhen it ran, in UTC.

The primary key is (module, name), so two modules may use the same migration name.

Running migrations from code

Migrator exposes the same operations to Rust code:

FunctionDoes
Migrator::run(&ctx, &MigrateOptions { pretend, step, schema_path })migrate; returns a MigrationReport
Migrator::rollback_with(&ctx, &RollbackOptions { step, batch, pretend })migrate:rollback
Migrator::reset, refresh, fresh_withthe other commands
Migrator::up, rollback, freshthe same, returning the module/name of each migration they ran
Migrator::status(&ctx)a MigrationStatus per migration: module, name, applied, batch, applied_at
Migrator::install, Migrator::dumpmigrate:install, schema:dump

A MigrationReport holds the dump it loaded, if any, and for each migration its module, name, batch, statements, notices and duration. In tests, TestApp::run_command runs the commands themselves:

Rust