The fnsql crate provides simple type-safe wrappers around SQL queries.
Instead of calling type-less .query() and .execute(), you call auto-generated
unique wrappers that are strongly typed: .query_<name>() and .execute_<name>().
You manually specify the input and output types, but only once, with the query,
in separation from the code that uses the query.
It's a very simple implementation that doesn't force any schema or ORM down
your throat, so if you are already using the sqlx or postgres crates,
you can gradually replace your type-less queries with the type-ful wrappers,
or migrate from an opinionated ORM.
Quick start (sqlx_sqlite)
fnsql!
The generated methods are extension methods on sqlx::SqlitePool:
# async
Quick start (postgres)
For postgres, named parameters are transformed to positional $1, $2, etc.
when the named attribute is used:
fnsql!
Generated methods are extension methods on postgres::Client and
postgres::Transaction<'a>, with both direct and prepared variants:
#
Generated API
sqlx_sqlite (sqlx::SqlitePool)
For each query <name>(p1: T1, p2: T2) -> [(O1, O2)], the following async
methods are generated on sqlx::SqlitePool:
| Method | Return Type | Description |
|---|---|---|
execute_<name>(&self, &p1, &p2) |
Result<u64, sqlx::Error> |
Execute, returns rows affected |
query_<name>(&self, &p1, &p2) |
Result<Vec<(O1, O2)>, sqlx::Error> |
Fetch all matching rows |
query_one_<name>(&self, &p1, &p2) |
Result<(O1, O2), sqlx::Error> |
Fetch exactly one row |
query_opt_<name>(&self, &p1, &p2) |
Result<Option<(O1, O2)>, sqlx::Error> |
Fetch zero or one row |
Named parameters in the SQL (:name) are automatically transformed to
positional $N placeholders at compile time.
A convert_row_<name>(row: SqliteRow) -> Result<(O1, O2), sqlx::Error>
function is also generated for manual row conversion.
postgres (postgres::Client / postgres::Transaction<'a>)
For each query <name>(p1: T1, p2: T2) -> [(O1, O2)], the following methods
are generated on both postgres::Client and postgres::Transaction<'a>:
| Method | Return Type | Description |
|---|---|---|
execute_<name>(&mut self, &p1, &p2) |
Result<u64, postgres::Error> |
Execute directly |
prepare_<name>(&mut self) |
Result<<name>Statement_, postgres::Error> |
Prepare statement |
execute_prepared_<name>(&mut self, stmt, &p1, &p2) |
Result<u64, postgres::Error> |
Execute prepared |
query_<name>(&mut self, &p1, &p2) |
Result<Vec<(O1, O2)>, postgres::Error> |
Fetch all rows |
query_prepared_<name>(&mut self, stmt, &p1, &p2) |
Result<Vec<(O1, O2)>, postgres::Error> |
Fetch all, prepared |
query_one_<name>(&mut self, &p1, &p2) |
Result<(O1, O2), postgres::Error> |
Fetch one row |
query_one_prepared_<name>(&mut self, stmt, &p1, &p2) |
Result<(O1, O2), postgres::Error> |
Fetch one, prepared |
query_opt_<name>(&mut self, &p1, &p2) |
Result<Option<(O1, O2)>, postgres::Error> |
Fetch opt row |
query_opt_prepared_<name>(&mut self, stmt, &p1, &p2) |
Result<Option<(O1, O2)>, postgres::Error> |
Fetch opt, prepared |
With the prepare-cache feature enabled, an additional prepare_cached_<name>()
method is available that uses fnsql::postgres::Cache.
Attributes
Each query declaration starts with attributes in square brackets:
#[<backend>, <attr2>, <attr3>, ...]
Backend (required, exactly one):
sqlx_sqlite— generates async methods onsqlx::SqlitePoolpostgres— generates sync methods onpostgres::Client/Transaction
Optional attributes:
test— generates an auto-test that runs the query with arbitrary valuestest(with=[other_query])— same astest, but runs prerequisite queries firstnamed(postgres only) — transforms:nameplaceholders to$1,$2, etc.conststr=<NAME>— generates apub const NAME: &strwith the query string
Parameters and return types
Parameters use Rust-like syntax: param_name: Type. The generated methods
accept references: ¶m_name. Supported types are anything that implements
the backend's respective trait (sqlx::Encode for sqlx, postgres::types::ToSql
for postgres).
Common type shortcuts:
stris accepted as a parameter type (maps to&str)[u8]is accepted as a parameter type (maps to&[u8])
Return types are optional and specified as -> [(T1, T2, ...)]. Each type
corresponds to a column in the result set. If omitted, the query is treated as
returning no data (DDL/DML).
Auto-generated tests
With the test attribute, fnsql generates a #[test] (or #[tokio::test] for
sqlx_sqlite) that opens an in-memory database, runs prerequisite queries via
test(with=[...]), and executes the query with arbitrary values. This validates
that your query syntax is correct without writing any test code.
To enable test compilation, add to your [dev-dependencies]:
= { = "1", = ["derive"] }