Sqlx, Cornucopia and ???

Posted on Fr 01 Dezember 2023 in Rust

In my spare time I try to improve my Rust skills usually by tinkering around with Axum web app stuff, digging into Htmx, ... In my job I design and manage batch processes for larger data sets. So I'm not terrified by writing SQL by hand. Rather the contrary: I like Postgres a lot and dislike ORMs that want to hide its power from me.

Sqlx

When doing my first steps in Rust I was quite happy to encounter Sqlx. It is nice, but has some problems from my personal point of view. I was especially annoyed by the fact that I could not deserialize Json results automatically into structs via Serde. I tried to fix this myself but failed. This might have been due to my limited Rust skills at that time. But the fully automatic checking against the database caused more trouble to me that it was helpfull.

Cornucopia

Cornucopia was the next project I discovered. It is still in an early stage, but the code generation works much better for me. At least until I started to write a lot of boiler plate code to be able use the generated code in a more convenient way. So I wrote my first declarative macro which was a nice learning experience, but turned out to be too limited.

The plan

Looking at the sources of Cornucopia I figured out that tokio-postgres and deadpool-postgres provide most of the basic building blocks. So I took a step back and wondered what functionality I need and would really like to have?

  • As mentioned, I would like to write SQL by hand. For small project it's convenient to have it in the Rust code. Neovim can highlight SQL in Rust strings, so that's not a problem.

  • Executing an SQL statement should be as simple as calling a function. So a database client instance, a function and the required parameters should be all that's needed to get data from the database.

  • The signature of the Rust function should declare its intention with as little "noise" as possible.

  • I would like to build nested Json structures in Postgres SQL. It should be as easy as possible to deserizalize the Json into Rust structs using Serde.

The question is: Is it feasibly for me to implement something like this myself in my spare time?

The proof of concept

If you need non-trivial real world Rust knowledge it is always a good idea to watch one of Jons videos. So did I and got the following code to compile:

#[select]
fn get_subjects() -> Vec<Subject> {
    "select id, name from subjects"
}

It can be called like this:

let subjects = get_subjects(&client).await?;

It might be a bit too much magic for some people, but I like it. The macro does the following:

  • Makes the function async.

  • Adds a &deadpool_postgres::Client as first parameter.

  • Wraps the return type in an approriate error type.

  • Replaces the body with code that uses the client to execute the statement and takes care of error handling and type conversion.

Future

Beside the overhead of learning new stuff everything worked out surprisingly easy. Next steps will probably be:

  • Implement macros for "insert", "give me one or none", "give me exactly one", ... That should be straight forward now.

  • Explore how smooth the type conversion between Rust and Postgres world will work out and what's possible.

  • Defining traits and implementing those for Client would probably lead to a nicer interface and client code. But I considered it too ambitious for my current skill level, so I deferred it to a future version.