.sql files
live in your package, a migration_database declaration in the build file names
the database they belong to, and rbs migrate applies and tracks them. It
supports PostgreSQL, MySQL, and SQLite, with checksummed history, a
database-side lock so concurrent migrators cannot interleave, and a dry-run mode.
Declaring a database
migration_database registers configuration — it is not a build target. It
produces nothing under rbs build, and you address it by name
(rbs migrate apply app_db), from any directory in the workspace: the command
loads every package, so the declaration is found wherever it lives.Migration files
Two kinds of file live inmigrations_dir:
- Versioned —
V{unix_timestamp}__{description}.sql. Runs exactly once, in version order (e.g.V1709141200__create_users_table.sql). - Repeatable —
R__{description}.sql. Re-runs whenever its checksum changes — the natural home for views, functions, and grants.
${key}:
${...} token fails the migration and points back at the
placeholders map on the rule.
Selecting the database
The connection string comes from the environment variable named bydatabase_url_env, read when you invoke rbs migrate:
SQLite makes a handy local fast path:
export DATABASE_URL=file:./dev.db runs
the same migration files against a local file.
Per-environment databases
The simplest per-environment story is exporting a different URL in each context — your shell, CI, a deploy pipeline. Because migrations against a cluster database are usually run over a port-forward or from inside the network, this composes naturally with a Kubernetes deploy:-e flag (or RBS_ENV) selects one of your
declared environments before workspace files
execute, and the env module is available in every build file — so a package
can register a different configuration per environment, for example a different
variable name:
Commands
apply
Applies all pending versioned migrations, in order, plus any repeatable migrations whose checksum changed. Behavior worth knowing:- Each migration runs in its own transaction. On failure the migration is recorded as failed and execution stops.
- A migration whose leading comments contain
-- rbs:no-transactionruns its statements individually outside any transaction — for statements likeCREATE INDEX CONCURRENTLYthat refuse one. - Concurrent applies are safe. A database-side lock serializes migrators, so two CI jobs applying at once cannot interleave.
- A pending migration versioned lower than one already applied is rejected
— the classic stale-branch-merged-late case. Pass
--out-of-orderto apply it anyway, or re-create it with a fresh timestamp. --dry-runprints the SQL that would run without touching the database (and skips the lock).
validate
Checks that reality matches the files on disk, exiting non-zero on any issue — put it in CI:- files modified after being applied (checksum mismatch)
- files deleted after being applied
- previously failed migrations that need attention