Migrate from dbmate
Convert a dbmate directory to Ptah's format, and read what happened to its timestamp versions.
A dbmate migration is one file with -- migrate:up and -- migrate:down
sections, named with a fourteen-digit timestamp. The conversion does not keep those
timestamps as version numbers, and it does keep transaction:false. Both are
worth knowing before you run it.
What you need
Section titled “What you need”- A
ptahbinary on yourPATH. Install Ptah if necessary. - A terminal and about five minutes.
No database server, Docker, or Go toolchain is required.
Convert the directory
Section titled “Convert the directory”Build the dbmate directory
Section titled “Build the dbmate directory”The second migration disables the transaction, which is what a
CREATE INDEX CONCURRENTLY needs on PostgreSQL. SQLite does not care, and the
directive is the point here rather than the engine.
mkdir -p ptah-from-dbmate/legacycd ptah-from-dbmatecat > legacy/20240101120000_create_users.sql <<'SQL'-- migrate:upCREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL);
-- migrate:downDROP TABLE users;SQLcat > legacy/20240215093000_email_index.sql <<'SQL'-- migrate:up transaction:falseCREATE UNIQUE INDEX users_email_idx ON users (email);
-- migrate:down transaction:falseDROP INDEX users_email_idx;SQLNew-Item -ItemType Directory ptah-from-dbmate/legacy | Out-NullSet-Location ptah-from-dbmate@'-- migrate:upCREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL);
-- migrate:downDROP TABLE users;'@ | Set-Content legacy/20240101120000_create_users.sql@'-- migrate:up transaction:falseCREATE UNIQUE INDEX users_email_idx ON users (email);
-- migrate:down transaction:falseDROP INDEX users_email_idx;'@ | Set-Content legacy/20240215093000_email_index.sqlPreview the conversion
Section titled “Preview the conversion”ptah migrations import --source-dir ./legacy --migrations-dir ./migrations --dry-runExpected output on standard output:
Dry run: would write 4 migration file(s) to ./migrations 0000000001_v20240101120000_create_users.up.sql 0000000001_v20240101120000_create_users.down.sql 0000000002_v20240215093000_email_index.up.sql 0000000002_v20240215093000_email_index.down.sqlRead those names before running anything. This is the part of a dbmate conversion that surprises people.
Run the import
Section titled “Run the import”ptah migrations import --source-dir ./legacy --migrations-dir ./migrationsExpected output on standard output:
Wrote 4 migration file(s) to ./migrationsWrote ./migrations/ptah.sum 0000000001_v20240101120000_create_users.up.sql 0000000001_v20240101120000_create_users.down.sql 0000000002_v20240215093000_email_index.up.sql 0000000002_v20240215093000_email_index.down.sqlWhat the conversion changed
Section titled “What the conversion changed”The timestamps are not the version numbers
Section titled “The timestamps are not the version numbers”20240101120000 became version 1, and 20240215093000 became version 2.
The original timestamp is kept, in the description: v20240101120000_create_users.
The order is preserved, which is what a migration directory needs. What is not preserved is the number a database already recorded.
This matters for a database dbmate has already migrated. Its
schema_migrations table holds the timestamps, and the converted directory
counts from one, so the two do not line up on any row. Applying the converted
directory to such a database would replay every migration it already has.
Adopt an existing database is the
procedure for recording the history as already applied; do that before the
first ptah migrations up against anything that is not empty.
A fresh database has no such problem, which is what this page uses.
transaction:false survives
Section titled “transaction:false survives”cat migrations/0000000002_v20240215093000_email_index.up.sqlExpected output on standard output:
-- +ptah no_transactionCREATE UNIQUE INDEX users_email_idx ON users (email);dbmate scopes the option to one direction and so does Ptah, so each side keeps what it asked for. A file that disables the transaction only on the way up converts to an up file carrying the directive and a down file that still runs inside one, which is what a multi-statement rollback wants: a failure partway through it rolls the whole thing back rather than leaving half the change behind.
Options the importer does not recognize are dropped from the SQL, as dbmate’s own directives are, and are not guessed at.
Apply the result
Section titled “Apply the result”Validate the sealed directory
Section titled “Validate the sealed directory”ptah migrations validate --dir ./migrationsExpected output on standard output:
OK: migrations directory matches ptah.sumApply the converted directory
Section titled “Apply the converted directory”ptah migrations up --db-url sqlite://app.db --migrations-dir ./migrationsExpected output includes, on standard output:
Current version: 0Total migrations: 2Pending migrations: 2Progress records on standard error carry timestamps and correlation IDs, so this page does not copy those volatile fields.
Verify the recorded state
Section titled “Verify the recorded state”ptah migrations status --db-url sqlite://app.db --migrations-dir ./migrationsExpected output includes, on standard output:
Current Version: 2Total Migrations: 2Applied Migrations: 2Pending Migrations: 0The recorded versions are 1 and 2, not the timestamps. That is the record
the converted directory and this database now share.
Clean up
Section titled “Clean up”cd ..rm -rf ptah-from-dbmateSet-Location ..Remove-Item -Recurse -Force ptah-from-dbmateWhere this leaves you
Section titled “Where this leaves you”dbmate also maintains db/schema.sql, a dump of the current schema. It has no
destination in a migration directory, and the import does not read it. It is
not useless: Ptah reads a .sql file as a desired-schema source, so it can
become the input to compare and drift
rather than something to delete.
Import an existing migration directory covers the other source tools and the format table.