bikeshedder / tusker

PostgreSQL migration management tool
The Unlicense
203 stars 17 forks source link

Tusker rewrite in Rust #37

Open bikeshedder opened 6 months ago

bikeshedder commented 6 months ago

I'm currently in process of rewriting tuksker in Rust.

Why?

  1. I love writing Rust code.
  2. I would prefer if I could just call a statically linked binary instead of having to bring an entire Python runtime when using Tusker in my devcontainer or deployment servers.
  3. migra and schemainspect have some unsolved issues. The lack of types makes it really hard to reason about the code.
  4. Tusker lacks a few features which I'd like to add but adding them to the current python code base doesn't feel right.

How is it going?

Head over to https://github.com/bikeshedder/tusker-rs and see for your own.

It doesn't work, yet. Don't bother trying it unless you want to start working on it. If that's your intention. Feel free to contact me and we can see what's the best way to collaborate.

bikeshedder commented 6 months ago

@dlight wrote at https://github.com/bikeshedder/tusker/issues/35#issuecomment-1885169258

Not sure if this thread is the right place to ask, but, did you check out postgres_migrator? It says in the readme:

Credits

  • migra for making it possible to diff schemas.
  • tusker was the inspiration for using temporary databases as diff targets. postgres_migrator adds the ability to generate and run versioned migrations and to perform compaction.

I've seen postgres_migrator but to me it is going in the wrong direction. It works just like tusker but runs migra and PostgreSQL inside a docker container. I want a lightweight tusker binary which can utilize my local running database. That also makes it compatible with a devcontainer without having to resort to awful solutions like DIND (Docker in Docker).

The most promising solution for me was renovate. I was not able to get it working and the whole idea of parsing the output of pgdump seams a bit weird to me. That project also seams to have stalled over the past few months.

Personally migra strikes a nice balance between sophistication and simplicity. PostgreSQL knows its schemas best. Accessing the pg_catalog.* tables for that purpose just makes sense.

The rewrite aims to keep the spirit of tusker while adding two new features:

dlight commented 6 months ago

Okay cool!

A simple type-safe query system. Just like the schemas you write SQL queries and tusker extracts the types from them via EXPLAIN queries. It works similar to sqlx but only takes care of the SQL queries and their types rather than providing a complete database driver.

Is this like cornucopia?

Cornucopia is really top notch; I think that you could use it in conjunction with tusker-rs.

bikeshedder commented 6 months ago

When I first heard about cornucopia a few weeks ago - I did my research before starting this project - I was very hyped about it. After some experiments with it I had two main problems with it:

The design I have in mind is a very small CLI tool and a single Query trait that can be derived:

-- get_post_by_id.sql, one file per query
SELECT id, author, text, created
FROM post
WHERE id = $1
#[derive(QueryOne)]
#[query(sql = "get_post_by_id.sql", row = Post)]
struct GetPostById {
    // order of params must match, types are checked
    pub id: i32,
}

struct Post {
    // column name and type are checked, order is irrelevant
    pub id: i32,
    pub author: String,
    pub text: String,
    pub created: time::OffsetDateTime,
}

And when using the query I'm aiming for this API:

let post = db.query(GetPostById { id: 1 }).await?;

I got a prototype working but it's still very rough around the edges. I'm still figuring out how to resolve the paths relative to the #[derive(QueryOne)] and how to type check the Post without having to add a #[derive(FromRow)] to the row structure as well. I might actually end up doing just that if I can't find another way. :shrug:

tbh. working on this future is quite frustrating but I haven't given up, yet. I'm tempted to ditch this code and just use cornucopia or sqlx ... :see_no_evil: