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
100 lines
6.1 KiB
Markdown
100 lines
6.1 KiB
Markdown
# 2. Anatomy of a Flyway migration run
|
|
|
|
[← 1. The problem and mental model](01-the-problem-and-mental-model.md) · [Index](../README.md) · Next: [3. Checksum validation →](03-checksum-validation.md)
|
|
|
|
The smallest possible Flyway setup is two SQL files and nothing else. This module's are
|
|
[`V1__create_customer.sql`](../src/main/resources/db/migration/V1__create_customer.sql) and
|
|
[`V2__seed_customer.sql`](../src/main/resources/db/migration/V2__seed_customer.sql), exercised by
|
|
[`FlywayHappyPathTest`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayHappyPathTest.java).
|
|
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.
|
|
|
|
```sql
|
|
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`](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](03-checksum-validation.md) is entirely about what happens when that
|
|
number stops matching.
|
|
|
|
<svg viewBox="0 0 700 190" xmlns="http://www.w3.org/2000/svg" font-family="monospace" font-size="13">
|
|
<rect x="10" y="70" width="150" height="50" rx="6" fill="#eef2ff" stroke="#4f46e5"/>
|
|
<text x="85" y="100" text-anchor="middle">V1__create_customer.sql</text>
|
|
<rect x="10" y="140" width="150" height="40" rx="6" fill="#eef2ff" stroke="#4f46e5"/>
|
|
<text x="85" y="164" text-anchor="middle">V2__seed_customer.sql</text>
|
|
<path d="M170 95 L230 95" stroke="#334155" stroke-width="2" marker-end="url(#a2)"/>
|
|
<path d="M170 160 L230 130" stroke="#334155" stroke-width="2" marker-end="url(#a2)"/>
|
|
<rect x="230" y="60" width="220" height="100" rx="6" fill="#ecfeff" stroke="#0891b2"/>
|
|
<text x="340" y="85" text-anchor="middle">Flyway.migrate()</text>
|
|
<text x="340" y="105" text-anchor="middle" font-size="11">for each file, in version order:</text>
|
|
<text x="340" y="122" text-anchor="middle" font-size="11">already in history? skip.</text>
|
|
<text x="340" y="139" text-anchor="middle" font-size="11">else: run it, record a row.</text>
|
|
<path d="M450 110 L520 110" stroke="#334155" stroke-width="2" marker-end="url(#a2)"/>
|
|
<rect x="520" y="30" width="170" height="160" rx="6" fill="#fdf2f8" stroke="#be185d"/>
|
|
<text x="605" y="55" text-anchor="middle">flyway_schema_history</text>
|
|
<text x="540" y="80" font-size="11">rank -1 TABLE</text>
|
|
<text x="540" y="100" font-size="11">rank 1 V1 SQL</text>
|
|
<text x="540" y="120" font-size="11">rank 2 V2 SQL</text>
|
|
<text x="540" y="145" font-size="11" fill="#475569">one row per migration,</text>
|
|
<text x="540" y="160" font-size="11" fill="#475569">named lowercase, quoted</text>
|
|
<defs><marker id="a2" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#334155"/></marker></defs>
|
|
</svg>
|
|
|
|
## 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:
|
|
|
|
```sql
|
|
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:
|
|
|
|
```sql
|
|
select "installed_rank" from "flyway_schema_history"
|
|
```
|
|
|
|
This module's own [`MigrationDiagnosticsController`](../src/main/java/com/ankurm/dbmigrations/web/MigrationDiagnosticsController.java)
|
|
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`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayHappyPathTest.java)
|
|
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`](../src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseHappyPathTest.java)
|
|
in chapter [9](09-liquibase-anatomy-of-an-update.md)). 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](https://documentation.red-gate.com/fd/migrations-271585107.html) — the full naming grammar, including undo and repeatable prefixes (`rel="nofollow"`).
|
|
- H2's identifier case sensitivity is documented on the [H2 SQL grammar page](https://h2database.com/html/grammar.html#name) under `DATABASE_TO_LOWER`/`DATABASE_TO_UPPER` (`rel="nofollow"`) — this module doesn't set either, so it runs on H2's default.
|