Skip to content
PtahDocs
v0.25.0

Graphic preview

100%
Page type: tutorial

Migrate from golang-migrate

Convert a golang-migrate directory to Ptah's format, seal it, and apply it to SQLite.

golang-migrate directories are pairs of NNN_name.up.sql and .down.sql files. Ptah reads that layout directly. This page converts one, including the case a real directory always has and a clean example never does: a migration whose down file was never written.

You will build the source directory, preview the conversion, run it, read what Ptah wrote for the missing rollback, and apply the result to a disposable SQLite database.

  • A ptah binary on your PATH. Install Ptah if necessary.
  • A terminal and about five minutes.

No database server, Docker, or Go toolchain is required.

The second migration has an up file and no down file. That is the wart this page is about, so create it exactly as shown.

Terminal window
mkdir -p ptah-from-golang-migrate/legacy
cd ptah-from-golang-migrate
cat > legacy/000001_create_users.up.sql <<'SQL'
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL
);
SQL
cat > legacy/000001_create_users.down.sql <<'SQL'
DROP TABLE users;
SQL
cat > legacy/000002_add_users_email_index.up.sql <<'SQL'
CREATE UNIQUE INDEX users_email_idx ON users (email);
SQL

--dry-run writes nothing and prints the files the import would produce.

Terminal window
ptah migrations import --from golang-migrate --source-dir ./legacy --migrations-dir ./migrations --dry-run

Expected output on standard output:

Dry run: would write 4 migration file(s) to ./migrations
0000000001_create_users.up.sql
0000000001_create_users.down.sql
0000000002_add_users_email_index.up.sql
0000000002_add_users_email_index.down.sql

Four files from three. Ptah’s format pairs every version, so the second migration gets a down file whether or not the source had one.

--from is optional here: the source tool is detected from the directory layout. Pass it when you want the run to fail rather than guess.

Terminal window
ptah migrations import --from golang-migrate --source-dir ./legacy --migrations-dir ./migrations

Expected output on standard output:

Wrote 4 migration file(s) to ./migrations
Wrote ./migrations/ptah.sum
0000000001_create_users.up.sql
0000000001_create_users.down.sql
0000000002_add_users_email_index.up.sql
0000000002_add_users_email_index.down.sql

The version numbers are widened to ten digits and the source directory is left untouched, so this step is safe to repeat and safe to abandon.

ptah.sum is written by the import. Ptah checksums every migration it applies, and the sum file is what later commands compare against.

The second migration had no down file. Ptah wrote one rather than leaving the pair incomplete:

Terminal window
cat migrations/0000000002_add_users_email_index.down.sql

Expected output on standard output:

-- No rollback was provided by the source migration.

That is the honest conversion of a migration that never had a rollback: a real file with no statements in it. The last section of this page shows what that costs, and this is the file to edit if you want a working rollback.

Terminal window
ptah migrations validate --dir ./migrations

Expected output on standard output:

OK: migrations directory matches ptah.sum

Editing a converted file without re-hashing fails here, before anything reaches a database.

Terminal window
ptah migrations up --db-url sqlite://app.db --migrations-dir ./migrations

Expected output includes, on standard output:

Current version: 0
Total migrations: 2
Pending migrations: 2

Progress records on standard error carry timestamps and correlation IDs, so this page does not copy those volatile fields.

Terminal window
ptah migrations status --db-url sqlite://app.db --migrations-dir ./migrations

Expected output includes, on standard output:

Current Version: 2
Total Migrations: 2
Applied Migrations: 2
Pending Migrations: 0

Both migrations are recorded as applied, and the directory the revision table describes is the converted one.

Rolling back past the empty rollback is refused

Section titled “Rolling back past the empty rollback is refused”
Terminal window
ptah migrations down --db-url sqlite://app.db --migrations-dir ./migrations --target 1 --confirm

Expected output includes, on standard error:

error: error running down migrations: migration 2 has a down file with no statements (0000000002_add_users_email_index.down.sql), so a rollback would change nothing and still move the version; write the rollback, or state that it has none with `-- +ptah empty_rollback` at the top of the down file

Nothing changed. A rollback that ran nothing and moved the version would leave the index in place, and the next apply of the second migration would fail on it.

Write the DROP INDEX the source never had:

Terminal window
cat > migrations/0000000002_add_users_email_index.down.sql <<'SQL'
DROP INDEX users_email_idx;
SQL

Seal the directory again, and roll back:

Terminal window
ptah migrations hash --dir ./migrations
Terminal window
ptah migrations down --db-url sqlite://app.db --migrations-dir ./migrations --target 1 --confirm

Expected output includes, on standard output:

Migrations to roll back: 1
✅ Migration rollback completed successfully!
Database is now at version: 1

The rollback is worth writing before the directory reaches an environment you cannot rebuild. A migration with nothing to undo says so instead.

Terminal window
cd ..
rm -rf ptah-from-golang-migrate

The legacy/ directory is no longer read by anything. Keep it in version control until the converted directory has been applied everywhere that matters, then delete it in a commit of its own.

If a database already has golang-migrate’s schema_migrations table with rows in it, do not run ptah migrations up against it. Record the history as already applied first: see Adopt an existing database.

Import an existing migration directory covers the other source tools and the format table.