Files
asmhatreandClaude Sonnet 5 67e1b2d8a9 db-migrations-flyway-liquibase: fix dead Redgate/Liquibase doc links and back the lock-defaults claim with a real transcript
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
2026-09-15 07:31:49 +00:00

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 says create customer, not create_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.
  • checksum is 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.
V1__create_customer.sql V2__seed_customer.sql Flyway.migrate() for each file, in version order: already in history? skip. else: run it, record a row. flyway_schema_history rank -1 TABLE rank 1 V1 SQL rank 2 V2 SQL one row per migration, named lowercase, quoted

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.