Possibility to build sqlx without runtime
heroin-moose opened this issue · 5 comments
Currently sqlx requires one of the runtime-* features enabled. However, when using sqlx in a crate that only defines types and derives FromRow it may be a little problematic, because crate users can have different runtimes.
Create 3 corresponding features like: sqlx-runtime-actix
, sqlx-runtime-tokio
, sqlx-runtime-async-std
which each enable the proper sqlx runtime feature like this: "sqlx/runtime-actix", "sqlx/runtime-tokio", "sqlx/runtime-async-std". Users of your library will then be able to select whichever feature they would like to use in sqlx through those options.
You can see how it's done in sea-orm: https://github.com/SeaQL/sea-orm/blob/master/Cargo.toml
Yeah, but do crate that only needs derives really need to advertise flags to match the chosen runtime?
TLDR
Larger projects may need to split db-related stuff into multiple crates and ability to compile without runtime would simplify that use case.
See use cases and longer summary below:
Crate with shared data types
I have a crate that defines shared data types for multi-crate project. Different downstream crates will use the data types in different ways - some with db-related stuff, some without (yes, there is serde
feature too). The only code in the crate related to sqlx
is something like this:
pub enum State {
Pass = 1,
Fail = 2,
Skip = 3,
....
}
#[cfg(feature="postgres")]
mod postgres {
impl Encode<'_, Postgres> for super::State {
...
}
impl Decode<'_, Postgres> for super::State {
...
}
impl Type<Postgres> for super::State {
...
}
}
On this level, the crate does not need to select runtime. It does not even know which runtime will be used by downstream crate.
The easiest solution is to 'just select runtime', but wrong selection can result in two or more crates selecting different runtime and conflict on compilation. (or just compile bunch of dead code, I did not experiment to much with actual setup).
Crate with db access layer
Similar case as above. I have a crate that implements a report on database. It will consist of input/output data types, some database queries. But again, it will not run those queries itself. The sole use is to be included in downstream crate that will start runtime and use shared access layer definition to run them.
If I try to do something like that, I will again end with same issue as in the first case.
Solution from sea-orm
I have checked the sea-orm
referred in previous comment. The solution would probably work, but I will force me to have runtime selection features duplicated in every crate that needs stuff from sqlx
. Whenever it uses runtime or not.
That seems like a lot of duplicate code in (possibly many) crates. And it is not easy solution to think about and implement correctly, especially for people who are just starting to use feature flags in their crates (like me :-))
My use case is three tier architecture with crates providing shared data, crates building queries using that data and finally service that runs it all together. Think dependency tree like this:
- foobar-service (connects to db and needs runtime)
- db-report-foo (just nice API over SQL queries, no need for runtime)
- db-data-foo (just reusable data types, no need for runtime)
- db-report-bar
- db-data-bar
- db-report-foobar
- db-data-foo
- db-data-bar
That's 6 crates that need to 'tunnel' runtime selection features instead of just setting it in the top level crate that actually needs it. Not even talking about need to compile runtime in 5 crates that will never need the code, wasting resources.
Attempt to solve the issue
I have checked the sql-core
crate, thinking that it will define just the shared data types, traits without forcing runtime selection. Unfortunately, the runtime must be selected even on this level.
There does not seem to be any lower-level sqlx
crate that would provide me with just traits/data types witch which I can develop my db access layer without tying it to one runtime.
Note 1: Yes, I could probably put it all into single crate and spare myself of some features-related difficulties. For smaller project, sure and that is how I actually started. But with growing number of reports to implement, splitting them into crates become necessity.
Note 2: I can't see spending the effort of duplicating feature flag selection code on many levels. It grates on me, but I will hard-code runtime selection on all levels (:disappointed:)
Summary
From my point of view, forcing runtime selection in every crate that uses sqlx
will get cumbersome when project gets larger or has need for organizing code in chunks due to readability/maintainability reasons. Any of following solutions would work:
- allow
sqlx
dependency without selecting runtime - allow
sqlx-core
dependency without selecting runtime for crates that do not need it - have different
sqlx-?
crates that expose parts of the functionality needed for writing reusable, db-related code (ie de/encoding, queries, ....)
The sea-orm
solution is workable, but pain to manage with multiple dependency levels and many crates.
Is there a technical reason why runtime must be selected? Or is it just so people do not forget to select runtime and then wonder why their code does not work?
I have the exact use case @heroin-moose and @zdenek-crha describe where I have a bunch of types in one crate and want to derive FromRow
on them without pulling the runtime in as a dependency of that crate.
This is fixed in the 0.7.0 release which is currently in an alpha cycle.