Course 5 · Module 1 — Migrating from Flyway
Flyway got you a long way. Numbered scripts, a clear order, a history table that knows what ran. But every change is another V__ file you keep forever, the order is load-bearing, and to know what your schema is you read the whole stack from the bottom up. Add a teammate and a branch or two, and you’re untangling version numbers instead of shipping schema.
Let’s move that database to SchemaSmith — without porting a single migration.
Flyway, concept for concept
Section titled “Flyway, concept for concept”| Flyway | SchemaSmith |
|---|---|
Versioned V__ migration scripts | Declarative table files — the end state, not the steps to reach it |
flyway_schema_history | No history table at all — your table definitions are the state |
flyway baseline (adopt an existing database) | SchemaTongs extract — cast the live database straight to files |
Repeatable R__ migrations | Idempotent by construction — quench re-runs converge, never double-apply |
Callbacks (afterMigrate.sql) | Before Scripts / After Scripts folders in the template |
| Out-of-order or missing-version drift | No order to get wrong — there’s one declared state, and the engine closes the gap |
The big shift is the second row. Flyway’s whole model is the history table — the ledger of what ran. SchemaSmith doesn’t have one, because it doesn’t care how you got here. It reads the shape you’ve got and the shape you declared, and makes them match.
Extract, and leave the ledger behind
Section titled “Extract, and leave the ledger behind”Open the lab’s before/ folder and you’ll see a real Flyway project — V1__create_shop.sql, V2__add_orderitem.sql. You don’t run them. The setup already applied their end state to shop_from_flyway, exactly the way flyway migrate would have — flyway_schema_history and all.
Now point SchemaTongs at that database. The settings file names the four tables you’re keeping:
"ShouldCast": { "ObjectList": "dbo.Customer,dbo.Product,dbo.SalesOrder,dbo.OrderItem" }cd sqlserverschematongs --ConfigFile:SchemaTongs.settings.json=== Casting Summary === Tables: 4 extracted, 0 errorsFour tables out — Customer, Product, SalesOrder, OrderItem. And flyway_schema_history? Not in the package. You named the tables you wanted; the ledger wasn’t on the list, so it never came along. That’s the whole trick — you don’t delete the history table, you just don’t invite it.
Now quench the package back to prove it captured the state:
schemaquench --ConfigFile:quench.settings.jsonThe first run adopts your tables — SchemaSmith stamps them as managed and stands up its own bookkeeping in a separate SchemaSmith schema, well away from your dbo tables. Run it again and nothing happens. No adds, no alters, no drops. A clean no-op is the proof: the package is a faithful cast of the live database, and you’re declarative now.
That old flyway_schema_history table is still sitting there, untouched. SchemaSmith left it exactly where it was — it’s an inert table you can drop whenever you feel like it. You traded a ledger you maintained for one you’ll never think about again.
What about callbacks and repeatable migrations?
Section titled “What about callbacks and repeatable migrations?”Two Flyway features people ask about:
-
Callbacks (
beforeMigrate.sql,afterMigrate.sql) — work you ran around a migration. In SchemaSmith that’s a.sqlfile dropped into the template’sBefore ScriptsorAfter Scriptsfolder. Concretely, the folder layout looks like this:Package/Templates/Main/Tables/ ← your declarative table filesAfter Scripts/Grant App Role Access.sql ← runs once, tracked, never re-runsRefresh Stats [ALWAYS].sql ← the [ALWAYS] tag re-runs it every quenchBy default a script runs once per database — SchemaSmith records it in its
CompletedMigrationScriptstable and skips it next time. Add[ALWAYS]to the filename for the every-run behavior a Flyway callback gives you. Same hook, declared in the package instead of named by Flyway convention. -
Repeatable migrations (
R__) — scripts Flyway re-runs when their checksum changes, usually to rebuild a view or proc. SchemaSmith handles that class of object declaratively: you declare the view or procedure once (a.sqlfile in the template’sViewsorProceduresfolder), and quench converges it whenever the file changes. No checksum, no re-run bookkeeping.
Per-engine notes
Section titled “Per-engine notes”The mechanism is identical on all three engines — whitelist the four tables, extract, quench to a no-op. Only the dialect’s names differ.
| SQL Server | PostgreSQL | MySQL | |
|---|---|---|---|
ObjectList form | dbo.Customer,… | public.customer,… (folded lowercase) | Customer,… (schema = database) |
| Tracker left behind | flyway_schema_history | flyway_schema_history | flyway_schema_history |
| Extract result | 4 tables, no tracker | 4 tables, no tracker | 4 tables, no tracker |
Check yourself: How do you keep flyway_schema_history out of your SchemaSmith package?
You don’t exclude it — you whitelist what you want. ShouldCast:ObjectList names the four shop tables, so SchemaTongs extracts exactly those and nothing else. flyway_schema_history isn’t on the list, so it never enters the package. The live table stays put, untouched; quench never manages it, and you can drop it on your own schedule.
Flyway built your shop one strike at a time, and kept a ledger of every blow. But the metal doesn’t remember the hammer — it only holds its shape. SchemaTongs reads that shape, casts it to files, and the ledger cools in the corner where you left it.
Got a Flyway setup with repeatable migrations or callbacks you’re not sure how to map? Email me at forgebarrett@schemasmith.com — I read every one.
Next up: Course 5 · Module 2 — Migrating from Liquibase, where there are two bookkeeping tables to leave behind, and changesets to map onto the declarative way.
Until then, may you set down your stack of scripts for good, and let the shape you’ve already forged carry you forward.
— Forge