Companion code for the Flyway vs Liquibase article: checksum validation, out-of-order and repeatable migrations, baselining an existing schema, Flyway Community's undo/diff/deploy stubs, concurrent-startup locking for both tools, Liquibase changeset identity and rollback (auto-generated vs explicit), a verified Liquibase 5.0.3 filename-caching defect, the new OSS license service, the FSL license change, and running both tools against one database. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Q6XdRjtsp4862EM44T7i9a
3.7 KiB
6. Baselining an existing database
← 5. Repeatable migrations · Index · Next: 7. Why there is no undo →
Every migration tool eventually meets a database it didn't create. Someone hand-ran DDL for five years, and now the team wants Flyway to take over from here — without dropping the schema and replaying history that never actually happened through Flyway.
FlywayBaselineTest
sets this up literally: a legacy_account table is created with plain JDBC, with one row already
in it, before Flyway is ever involved. Pointing an unconfigured Flyway at that database refuses
outright — this is the same safety check from chapter 1, and
it's what makes baselining necessary rather than optional:
FlywayException: Found non-empty schema(s) "PUBLIC" but no schema history table. Use baseline() or set baselineOnMigrate to true to initialize the schema history table.
Turning on spring.flyway.baseline-on-migrate=true (with baseline-version=1 and a
baseline-description) fixes it. V1__init.sql — written to describe the schema that already
exists — never actually runs; instead Flyway inserts a BASELINE-typed row claiming version 1 is
already accounted for, and only V2__add_status_column.sql executes for real:
installed_rank | version | description | type | success
---------------+---------+-------------------------------------------+----------+--------
-1 | NULL | << Flyway Schema History table created >> | TABLE | true
1 | 1 | pre-flyway schema | BASELINE | true
2 | 2 | add status column | SQL | true
(from docs/output/05-flyway-baseline.txt)
The pre-existing row survives untouched, and gains the new column:
ID | OWNER | STATUS
---+-----------------+-------
1 | pre-flyway-data | ACTIVE
Trap: the baseline row'sdescriptionis not a fixed string. It is exactly whateverspring.flyway.baseline-descriptionwas set to — in this test, literally"pre-flyway schema". A different, fixed marker —<< Flyway Schema History table created >>— belongs to a different row (rank-1, typeTABLE), created the moment the history table itself is created, whether or not baselining ever happens. Confusing the two is an easy way to write a broken assertion or a broken monitoring query — this module's own first draft did exactly that.
Going deeper
baselineVersiondecides what "already accounted for" means, and it matters which value you pick — chapter 14 shows the same mechanism used withbaseline-version=0instead of1, for a database whose existing schema doesn't match any of your migration files at all.- Flyway
baselinecommand reference (rel="nofollow") — the one-timeflyway baselineCLI/API call this test'sbaselineOnMigrate=truetriggers automatically on first startup. - Baselining is a one-way door in the sense that matters: once version 1 is marked as baselined, Flyway will never again check whether the schema it describes actually matches
V1__init.sql's content — that specific file's checksum is simply never looked at again for this database.