Skip to content

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.

FlywaySchemaSmith
Versioned V__ migration scriptsDeclarative table files — the end state, not the steps to reach it
flyway_schema_historyNo 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__ migrationsIdempotent 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 driftNo 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.

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" }
Terminal window
cd sqlserver
schematongs --ConfigFile:SchemaTongs.settings.json
=== Casting Summary ===
Tables: 4 extracted, 0 errors

Four 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:

Terminal window
schemaquench --ConfigFile:quench.settings.json

The 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 .sql file dropped into the template’s Before Scripts or After Scripts folder. Concretely, the folder layout looks like this:

    Package/Templates/Main/
    Tables/ ← your declarative table files
    After Scripts/
    Grant App Role Access.sql ← runs once, tracked, never re-runs
    Refresh Stats [ALWAYS].sql ← the [ALWAYS] tag re-runs it every quench

    By default a script runs once per database — SchemaSmith records it in its CompletedMigrationScripts table 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 .sql file in the template’s Views or Procedures folder), and quench converges it whenever the file changes. No checksum, no re-run bookkeeping.

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 ServerPostgreSQLMySQL
ObjectList formdbo.Customer,…public.customer,… (folded lowercase)Customer,… (schema = database)
Tracker left behindflyway_schema_historyflyway_schema_historyflyway_schema_history
Extract result4 tables, no tracker4 tables, no tracker4 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