Dbmate: A lightweight, framework-agnostic database migration tool
github.com
github.com
There's very little guesswork about what it's going to do to a database because your migrations are raw SQL statements with no additional layers of complexity or translation.
I've been using raw SQL for migrations in some projects for years.
Boring and predictable. Just how I like it.
Instead of one file with an up and down, there could be two files where each has a predicate and then an action, where the predicate would run to determine if the migration has been applied or not.
I can also imagine edge cases like a migration dropping a column and another recreating it where it may be unclear what the state is, and honestly you don't want to be surprised when you migrate thousands of paying customers' data.
You can do something like this:
db["cats"].create({
"id": int,
"name": str,
"weight": float,
}, pk="id", transform=True)
The transform=True parameter means "if the table already exists but does not match the provided schema, transform it to add missing columns etc".I don't generally recommend it though, it's a risky way of working compared to stateful migrations.
Here are the tests for that option: https://github.com/simonw/sqlite-utils/blob/577078fe01da6d87... - and the accompanying issue: https://github.com/simonw/sqlite-utils/issues/467
Entity Framework (both Core and legacy) does that, also DbUp uses migrations table for those that prefer lighter ORMs.
My tool Skeema, first released in 2016, provides declarative schema management for hundreds of MySQL/MariaDB based companies, including GitHub, SendGrid, Cash App, Wix, Etsy, and many others you have likely heard of. Safety is the primary consideration throughout all of Skeema's design: https://www.skeema.io/docs/features/safety/
For Postgres, a few declarative solutions include sqldef, Migra, Tusker (which builds on Migra), and Atlas.
[1] https://www.skeema.io/blog/2019/01/18/declarative/
[2] https://www.skeema.io/blog/2023/10/24/stored-proc-deployment...
MyBatis Migrations[0] does that with the use of a migrations management table.
As to why most, if not all, SQL migration tools are stateful, "the good ones" often have migration descriptions and timestamps as well. Since a persistent store is by definition stateful, having tooling state stored alongside the concepts managed makes sense in many cases.