30 comments

[ 5.6 ms ] story [ 57.9 ms ] thread
I have used this as well as many of the other lower-level db drivers (which don't check your SQL at compile time) and I can say I much prefer the latter.

My issues with SQLx when I first tried it were that it was really awkward (nigh impossible) to abstract away the underlying DB backend, I expect those issues are fixed now but for some simple apps it's nice to be able to start with SQLite and then switch out with postgres.

Then I wanted to dockerize an SQLx app at one point and it all becomes a hassle as you need postgres running at compile time and trying to integrate with docker compose was a real chore.

Now I don't use SQLx at all. I recommend other libraries like sqlite[1] or postgres[2] instead.

SQLx is a nice idea but too cumbersome in my experience.

[1]: https://docs.rs/sqlite/latest/sqlite/ [2]: https://docs.rs/postgres/latest/postgres/

I know it's annoying (and apparently there is a solution for generating the required files before the build), but in these kinds of situations Go and Rust are great for doing a static build on the system and then copying into a scratch image.

Versus Python and Node often needing to properly link with the system they'll actually be running in.

How long ago did you try SQLx? Not necessarily promoting SQLX, but the `query_as` which lets one make queries without the live database macro has been around for 5 years [1].

For lower level libraries there is also the more downloaded SQLite library, rusqlite [2] who is also the maintainer of libsqlite3-sys which is what the sqlite library wraps.

The most pleasant ORM experience, when you want one, IMO is the SeaQl ecosystem [3] (which also has a nice migrations library), since it uses derive macros. Even with an ORM I don't try to make databases swappable via the ORM so I can support database-specific enhancements.

The most Rust-like in an idealist sense is Diesel, but its well-defined path is to use a live database to generate Rust code that uses macros to then define the schema-defining types which are used in the row structs type/member checking. If the auto-detect does not work, then you have to use its patch_file system that can't be maintained automatically just through Cargo [4] (I wrote a Makefile scheme for myself). You most likely will have to use the patch_file if you want to use the chrono::DateTime<chrono::Utc> for timestamps with time zones, e.g., Timestamp -> Timestamptz for postgres. And if you do anything advanced like multiple schemas, you may be out of luck [5]. And it may not be the best library for you if want large denormalized tables [6] because compile times, and because a database that is not normalized [7], is considered an anti-pattern by project.

If you are just starting out with Rust, I'd recommend checking out SeaQl. And then if you can benchmark that you need faster performance, swap out for one of the lower level libraries for the affected methods/services.

[1] https://github.com/launchbadge/sqlx/commit/47f3d77e599043bc2...

[2] https://crates.io/crates/rusqlite

[3] https://www.sea-ql.org/SeaORM/

[4] https://github.com/diesel-rs/diesel/issues/2078

[5] https://github.com/diesel-rs/diesel/issues/1728

[6] https://github.com/diesel-rs/diesel/discussions/4160

[7] https://en.wikipedia.org/wiki/Database_normalization

Why would you want to abstract away the underlying database? Wouldn't it better to already use the target DB to cattch potential issues earlier? Also to avoid creating another layer of indirection, potentially complecting the codebase and reducing performance?
> Wouldn't it better to already use the target DB to cattch potential issues earlier?

The target DB can change as a project goes from something mildly fun to tinker with to something you think might actually be useful.

Also I personally find that SQLite is just nice to work with. No containers or extra programs, it just does what you ask it to, when you ask it to

I used SQLx with an SQLite database and ran into connection pool problems that would cause the database to be unexpectedly dropped.

The issues I saw seem to be related to these issues:

https://github.com/launchbadge/sqlx/issues/3080

https://github.com/launchbadge/sqlx/issues/2510

The problems did not manifest until the application was under load with multiple concurrent sessions.

Troubleshooting the issue by changing the connection pool parameters did not seem to help.

I ended up refactoring the application's data layer to use a NoSQL approach to work around the issue.

I really like the idea of SQLx and appreciate the efforts of the SQLx developers, but I would advise caution if you plan to use SQLx with SQLite.

I've been using sqlx with postgres for several months now on a production server with decent query volume all day long. It has been rock solid.

I find writing sql in rust with sqlx to be far fewer lines of code than the same in Go. This server was ported from Go and the end result was ~40% fewer lines of code, less memory usage and stable cpu/memory usage over time.

I have never used SQLx, but the best SQL integration I can think of is LINQ. How does this compare to that?
sqlx is my favorite way of working with databases in Rust hands down.

I've tried alternatives like Diesel and sea-orm. To be honest, I feel like full-blown ORMs really aren't a very good experience in Rust. They work great for dynamic languages in a lot of cases, but trying to tie in a DB schema into Rust's type system often creates a ton of issues once you try to do anything more than a basic query.

It's got a nice little migration system too with sqlx-cli which is solid.

Love SQLx for my Rust projects. I would like to figure out a great way to use the compile time checks in python or js projects, but haven't explored it yet.
I've read the GitHub page but I still have no idea what problems it solves or why I should care about this library?
SQLx and F# type-providers are probably the best developer experience for writing database access code. I wish more languages had something equivalent.
SQLx is great, but I really wish they had a non-async interface. I had to switch a project from sqlx to rusqlite seemingly just due to the overhead of the async machinery. Saw a 20x latency reduction that I narrowed down to "probably async" (sort of hard to tell, I find it very difficult to do perf analysis of async code). I try to avoid discussing async so as to not come off as a frothing-at-the-mouth-chest-thumping-luddite but honestly, if sqlx had a non-async interface I'd be very happy to accept the "you don't need to use it" argument. its the only place where I don't feel like I really have a choice.
Async does not incur 20x slowdowns when you're I/O bound. It would be ridiculous for copying a few bytes to be slower than a syscall. This sounds like mutex issues, or WAL config, or something like that.
I just chucked something together to try and demonstrate. I don't see the massive 20x slowdown, only about a 3x slowdown (5x on release build). Still enough to be painful for the use case in question.

https://github.com/Moggers/rusqlitebenchmark

Do you think you could look through it and point out what you think the reason is? I think they've both got the same WAL and mutex settings. Its a very contrived and synthetic example but actually somewhat representative of what the original code wanted to do.

I find it kind of baffling that this toolkit is so popular when it makes handling database joins so difficult. After bashing my head against it for a while, I moved to Diesel, and while that has its own set of problems, I am generally able to get through them without resorting to horrible hacks or losing compile time checks.
Quite similar to manifold-sql[1], which is arguably better integrated into Java than SQLx is into Rust. Inline native SQL in Java is *inherently type-safe*, no mapping -- query types, query results, query parameters all projected types at compile-time.

    int year = 2019;
    . . .
    for(Film film: "[.sql/] select * from film where release_year > :rel_year".fetch(year)) {
        out.println(film.title);
    }
1. https://github.com/manifold-systems/manifold/blob/master/man...
One thing I don't usually see addressed with the pure-sql approaches is how to handle dynamic query building. The most common example being large configurable forms that display a data grid. Kysely[1] does a good job of starting from this angle, but allowing something like specifying the concrete deserialization type similar to the libraries here.

I'm a big fan of sql in general (even if the syntax can be verbose, the declarative nature is usually pleasant and satisfying to use), but whenever dynamic nature creeps in it gets messy. Conditional joins/selects/where clauses, etc

How do folks that go all in on sql-first approaches handle this? Home-grown dynamic builders is what I've seen various places I've work implement in the past, but it's usually not built out as a full API and kind of just cobbled together. Eventually they just swap to an ORM to solve the issue.

* [1] https://kysely.dev

One approach is to create views for the required data and then just select the columns which are needed. The joins will be pruned by the query planner if they are not needed, so there is no need for conditional joins.
Yeah this definitely makes sense, and is good database API design as well.
> dynamic query building

it's not (really) addressed by sqlx (intentionally), in the same way most ORM features are not addressed

but to some degree this is what is so nice about sqlx it mainly(1) provides the basic SQL functionality and then let you decide what to use on top of it (or if to use anything on top).

If you need more e.g. the sea-* ecosystem (sea-query, sea-orm) might fulfill you needs.

(1): It can compile time check "static" queries (i.e. only placeholders) which is a bit more then "basic" features, but some projects have to 99+% only static queries in which case this feature can move SQLx from "a building block for other sql libs" to "all you need" to keep dependencies thinner.

I find that interpolating strings works pretty well for this use case (which actually switchd TO string interpolation from ORMs at a previous job of mine).

But this is conditional on either your database or your minimal abstraction layer having support for bindings arrays of data with a single placeholder (which is generally true for Postgres).

I use a fork of sqlx in SQLPage [1]. I think my main complaint about it is runtime errors (or worse, values decoded as garbage) when decoding SQL values to the wrong rust type.

* [1] https://sql-page.com/

If anyone is looking for a similar thing in F#, complete with automatic type generation etc, have a look at SqlHydra.
Just coming here to make a prediction: using raw SQL is not great for anything but very simple cases. You can make it type-safe, but that becomes tricky once things become dynamic.

But the real problem is ergonomy. The better solution in almost any language is to leverage the syntax of your language to allow for as much (non-macro) type-safety and auto-completion as possible.

For example instead of:

   SELECT country, COUNT(*) as count
   FROM users
   GROUP BY country
   WHERE organization = ?
That should be

   select("country", count("\*").as("count))
   .from("users")
   .groupBy("country")
   .where("organization".=(yourVariable))
[note that it matches SQL, not the language's collections function's names.]

As you see, that's also nice because now you can actually use variables easy - and even use pure rust to decide dynamically on things.

You can further increase typesafety if you want by doing things like `.from(table("users"))` and running extra checks on that table() part, similar to what the lib probably does. Also, sometimes you might have to make a compromise on your syntax and things like `"organization".=(yourVariable)` might have to be slightly rewritten.

Still, I think that people will rather end up with a library like I described, unless the SQL is very basic/static.

I've used SQLx for a couple of projects (MariaDB and SQLite) its good, it does the thing though it takes a little bit of getting used to. The fact that it can check queries at compile time is its biggest strength.
I am not much into Rust ATM. I am quite comfortable with C++. So here it goes my question:

I use sqlpp11 in C++.

I generate code and I can use it with strong typing by including some headers. This Rust crate seems to provide compile-time checking.

But it will give me code-completion? It is very nice that by pressing '.' you know what you potentially have.

I first went to sqlx thinking it would be like JOOQ for Rust, but that wasn't the case. It's a pretty low-level library and didn't really abstract away the underlying DBs much, not to mention issues with type conversions. We've since just used rust-postgres.