Migrate from Liquibase
Convert a Liquibase changelog, and learn which of your changesets carry SQL and which do not.
What decides whether a Liquibase changeset converts is not the file format. It is what the changeset carries.
A changeset that carries SQL converts, whether you wrote it as formatted SQL or
as <sql> inside XML. A changeset that carries a typed change — <createTable>,
<addColumn>, and the rest of Liquibase’s database-independent vocabulary — has
no SQL to carry over, and Ptah refuses it by name rather than generating SQL it
would have to guess the dialect for.
Carrying SQL is necessary and not sufficient. context, contexts, labels
and preConditions decide at run time whether a changeset applies, and a
migration directory cannot express that, so a changeset carrying one is refused
as well. Importing it would turn a conditional history into an unconditional
one, which is a worse outcome than a refusal.
This page converts what converts and reads both refusals.
What you need
Section titled “What you need”- A
ptahbinary on yourPATH. Install Ptah if necessary. - A terminal and about eight minutes.
No database server, Docker, or Go toolchain is required.
Convert a formatted-SQL changelog
Section titled “Convert a formatted-SQL changelog”Build the changelog
Section titled “Build the changelog”mkdir -p ptah-from-liquibase/legacycd ptah-from-liquibasecat > legacy/001-users.sql <<'SQL'--liquibase formatted sql
--changeset alice:1CREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL);--rollback DROP TABLE users;
--changeset alice:2CREATE UNIQUE INDEX users_email_idx ON users (email);--rollback DROP INDEX users_email_idx;SQLNew-Item -ItemType Directory ptah-from-liquibase/legacy | Out-NullSet-Location ptah-from-liquibase@'--liquibase formatted sql
--changeset alice:1CREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL);--rollback DROP TABLE users;
--changeset alice:2CREATE UNIQUE INDEX users_email_idx ON users (email);--rollback DROP INDEX users_email_idx;'@ | Set-Content legacy/001-users.sqlRun 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_alice_1.up.sql 0000000001_alice_1.down.sql 0000000002_alice_2.up.sql 0000000002_alice_2.down.sqlEach changeset became a migration, named from its author and id. The
--rollback line became the down file, which is what it already was.
What an XML changelog does
Section titled “What an XML changelog does”A changeset carrying SQL converts
Section titled “A changeset carrying SQL converts”mkdir -p xml/legacycat > xml/legacy/changelog.xml <<'XML'<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"> <changeSet id="1" author="alice"> <sql>CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);</sql> <rollback>DROP TABLE users;</rollback> </changeSet></databaseChangeLog>XMLNew-Item -ItemType Directory xml/legacy | Out-Null@'<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"> <changeSet id="1" author="alice"> <sql>CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);</sql> <rollback>DROP TABLE users;</rollback> </changeSet></databaseChangeLog>'@ | Set-Content xml/legacy/changelog.xmlptah migrations import --from liquibase --source-dir ./xml/legacy --migrations-dir ./xml/migrationsExpected output on standard output:
Wrote 2 migration file(s) to ./xml/migrationsWrote ./xml/migrations/ptah.sum 0000000001_alice_1.up.sql 0000000001_alice_1.down.sqlXML is read. Nothing about the format stops the conversion.
A changeset carrying a typed change is refused, by name
Section titled “A changeset carrying a typed change is refused, by name”mkdir -p typed/legacycat > typed/legacy/changelog.xml <<'XML'<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"> <changeSet id="1" author="alice"> <createTable tableName="users"> <column name="id" type="int"/> </createTable> </changeSet></databaseChangeLog>XMLNew-Item -ItemType Directory typed/legacy | Out-Null@'<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"> <changeSet id="1" author="alice"> <createTable tableName="users"> <column name="id" type="int"/> </createTable> </changeSet></databaseChangeLog>'@ | Set-Content typed/legacy/changelog.xmlptah migrations import --from liquibase --source-dir ./typed/legacy --migrations-dir ./typed/migrationsExpected output on standard error:
error: parse liquibase source: liquibase changeset alice_1 in "changelog.xml" uses <createTable>, which is not SQL text and which Ptah does not generate per dialect; rewrite it as a `sql` change or import it by handThe message names the changeset, the file and the element, so a changelog with
one offender tells you which one on the first run. Work through them by
rewriting each as a sql change in Liquibase first, where you can still run
liquibase update-sql to see what it would have generated, and convert once
they all carry SQL.
That is also the honest reason for the refusal: a typed change is database-independent, and turning it into SQL means choosing a dialect. Ptah will not choose one on your behalf inside an import.
A changeset carrying a selector is refused too
Section titled “A changeset carrying a selector is refused too”The changeset below carries SQL, so the rule above stops short of deciding it.
context="staging" is what makes the difference.
mkdir -p conditional/legacycat > conditional/legacy/changelog.xml <<'XML'<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"> <changeSet id="1" author="alice" context="staging"> <sql>CREATE TABLE users (id INTEGER PRIMARY KEY);</sql> </changeSet></databaseChangeLog>XMLNew-Item -ItemType Directory conditional/legacy | Out-Null@'<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"> <changeSet id="1" author="alice" context="staging"> <sql>CREATE TABLE users (id INTEGER PRIMARY KEY);</sql> </changeSet></databaseChangeLog>'@ | Set-Content conditional/legacy/changelog.xmlptah migrations import --from liquibase --source-dir ./conditional/legacy --migrations-dir ./conditional/migrationsExpected output on standard error:
error: parse liquibase source: liquibase changeset alice_1 in "changelog.xml" is conditional on context; a migration directory has no equivalent, so importing it would turn a conditional history into an unconditional one -- split the changelog or import it by handcontexts, labels and preConditions are refused the same way. Split the
changelog per environment in Liquibase first, where the selector still means
something, and convert each result separately.
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:
Total 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: 0Clean up
Section titled “Clean up”cd ..rm -rf ptah-from-liquibaseSet-Location ..Remove-Item -Recurse -Force ptah-from-liquibaseWhere this leaves you
Section titled “Where this leaves you”Contexts, labels and preconditions have no destination in a migration directory, which is why a changeset carrying one is refused rather than imported without it. Deciding what they meant is work that belongs in Liquibase: a context is usually the reason a changeset ran in one environment and not another, and once the changelog is split along that line each half converts.
If a database already has Liquibase’s DATABASECHANGELOG table, 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.