Skip to content
PtahDocs
v0.8.0
Page type: how-to

Import from another tool

Convert a golang-migrate, Goose, Flyway, Liquibase, or dbmate migration directory into Ptah's native format.

Your project already has migration history in another tool, and you want Ptah to own it from here — without hand-rewriting years of files. This page shows how ptah migrations import converts that history, what the converted directory looks like, and how to pick up the lifecycle afterward.

Prerequisites: a built ptah binary and the source tool’s migration directory. The example imports a golang-migrate directory; Goose, Flyway, Liquibase, and dbmate work the same way.

A golang-migrate directory in which the second migration has no down file:

db/migrations/
000001_create_users.up.sql
000001_create_users.down.sql
000002_add_posts.up.sql

--dry-run lists what would be written without writing it:

Terminal window
ptah migrations import \
--source-dir ./db/migrations \
--migrations-dir ./migrations \
--dry-run

Expected output includes:

Dry run: would write 4 migration file(s) to ./migrations
0000000001_create_users.up.sql
0000000001_create_users.down.sql
0000000002_add_posts.up.sql
0000000002_add_posts.down.sql
Terminal window
ptah migrations import \
--source-dir ./db/migrations \
--migrations-dir ./migrations

Expected output includes:

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

Import converts the source files into Ptah’s native NNNNNNNNNN_name.up.sql / .down.sql layout, preserving version order, and rewrites ptah.sum itself — validation passes immediately:

Terminal window
ptah migrations validate --dir ./migrations
OK: migrations directory matches ptah.sum

A source migration with no rollback gets a placeholder down file, so the directory satisfies the down-file contract:

-- No rollback was provided by the source migration.

Review placeholder downs before relying on rollback through those versions.

The source tool is auto-detected from the directory layout; set --from to assert it explicitly (golang-migrate, goose, flyway, liquibase, dbmate).

Tool Notes
golang-migrate NNN_name.up.sql / .down.sql pairs.
Goose Annotated single files (-- +goose Up / -- +goose Down); the exact whole-file line -- +goose NO TRANSACTION becomes -- +ptah no_transaction on both imported directions.
Flyway Including dotted versions, undo U__ scripts, and repeatable R__ scripts.
Liquibase Formatted-SQL changelogs (--changeset / --rollback), and XML, YAML or JSON changesets that carry SQL; a changeset that carries a typed change or a selector is refused by name.
dbmate Annotated single files (-- migrate:up / -- migrate:down); a directive keeps its options out of the SQL, and transaction:false on one direction becomes -- +ptah no_transaction on that direction alone.

This is native Ptah-format import, distinct from the Atlas-compatible migrate import verb of the ptah-compat binary, which writes an Atlas-format directory with atlas.sum.

Flyway’s R__name.sql re-runs whenever its body changes. Import converts it to an ordinary one-time migration ordered after every versioned file, because the destination format has no reapply semantics to convert it into.

That changes what editing the file means. The converted file is checksummed like every other migration: ptah migrations validate refuses the directory once you edit it, and re-hashing only seals the files to each other. The database separately recorded the checksum of what ran, so ptah migrations up refuses with a checksum mismatch until the file is back, and the edited body never runs.

The recovery has an order. Put the file back as it was first, because the refusal precedes every migration in the directory, and then carry the change in a new versioned migration. Re-importing the source directory is the other way back, where that directory is still the source of truth. ptah migrations repair is not the route — it edits recorded state, and nothing has gone wrong. Migrate from Flyway runs the whole sequence.

The Atlas-compatible migrate import verb of the ptah-compat binary places a converted repeatable on a reserved version slot above every versioned migration instead, because an Atlas directory takes each migration’s version from the file name.

The converted directory is ordinary Ptah history; what comes next depends on the database:

  • The database already has the schema (it ran the source tool’s migrations): record the history as applied without executing it — ptah migrations baseline, covered step by step in Adopt an existing database.
  • A fresh database: apply the directory from version zero.

Either way, future changes follow the regular loop starting at Generate migrations.

Import never overwrites. Running it into a directory that already contains a converted file fails (exit 2) and writes nothing:

error: refusing to overwrite existing migration file "0000000001_create_users.up.sql" in ./migrations

Point --migrations-dir at an empty directory, or remove the partial result and rerun.

A Liquibase changeset with nothing to convert is rejected by name. A typed change such as <createTable> carries no SQL, and context, contexts, labels and preConditions decide at run time whether a changeset applies, which a migration directory cannot express. The message names the changeset, the file and the construct. Migrate from Liquibase works through both.