Several documentation.red-gate.com and liquibase.com URLs added in the previous commit had since moved (Redgate restructured its docs under /flyway/reference/, Liquibase Pro pricing moved to /pricing); this repoints them at the current, verified-200 pages. Also captures docs/output/16-liquibase-lock-defaults-javap.txt, the trimmed javap output backing chapter 11's claim about Liquibase's default lock-poll and lock-wait settings, which chapter 11 previously asserted without a committed artifact to back it. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Q6XdRjtsp4862EM44T7i9a
6.1 KiB
2. Anatomy of a Flyway migration run
← 1. The problem and mental model · Index · Next: 3. Checksum validation →
The smallest possible Flyway setup is two SQL files and nothing else. This module's are
V1__create_customer.sql and
V2__seed_customer.sql, exercised by
FlywayHappyPathTest.
The filename is the metadata: V for versioned, 1 or 2 as the version, two underscores,
then a description Flyway derives straight from the rest of the filename.
create table customer (
id bigint generated by default as identity primary key,
name varchar(120) not null,
email varchar(200) not null unique
);
That's the whole of V1. After both files run once, flyway_schema_history looks like this
(quoted verbatim from
docs/output/01-flyway-happy-path.txt):
installed_rank | version | description | type | checksum | success
---------------+---------+-------------------------------------------+-------+-------------+--------
-1 | NULL | << Flyway Schema History table created >> | TABLE | NULL | true
1 | 1 | create customer | SQL | 1461549807 | true
2 | 2 | seed customer | SQL | -1214875726 | true
Two things worth noticing, both easy to get wrong if you only skim the docs:
- The description column drops underscores for spaces. The file is
V1__create_customer.sql; the row sayscreate customer, notcreate_customer. This module's own test assertions got this wrong on the first pass — they were written expecting the underscore to survive, and the real output corrected them. checksumis a signed 32-bit CRC of the file's content, not a hash of the SQL statements Flyway ran. Chapter 3 is entirely about what happens when that number stops matching.
A case-folding trap worth knowing before you write your own query
That last line in the diagram — "named lowercase, quoted" — is not decoration. Flyway creates and
queries its own table using quoted, lowercase identifiers:
"flyway_schema_history", "installed_rank", and so on. H2, like most databases, folds
unquoted identifiers to uppercase by default. So this innocent-looking query, run from your own
code against the exact same database, finds nothing:
select installed_rank from flyway_schema_history -- looks for FLYWAY_SCHEMA_HISTORY — not found
The fix is to quote it the same way Flyway does:
select "installed_rank" from "flyway_schema_history"
This module's own MigrationDiagnosticsController
hit exactly this while it was being written — the first version queried Flyway's table
unquoted and silently got "table not found" back. Every test in this module that reads
flyway_schema_history directly (see
FlywayHappyPathTest
and the rest of the flyway package) quotes it for exactly this reason. Liquibase's own tables
don't need this — its unquoted DDL for DATABASECHANGELOG folds consistently with H2's default,
so plain, unquoted select ... from databasechangelog works fine (see
LiquibaseHappyPathTest
in chapter 9). The asymmetry is real, verified by running
both, and it is exactly the kind of thing that costs you twenty minutes the first time you write a
raw report query against a Flyway-managed schema.
Going deeper
- Flyway migration naming — the full naming grammar, including undo and repeatable prefixes (
rel="nofollow"). - H2's identifier case sensitivity is documented on the H2 SQL grammar page under
DATABASE_TO_LOWER/DATABASE_TO_UPPER(rel="nofollow") — this module doesn't set either, so it runs on H2's default.