# 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. 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: ```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.