Using your generated queries
Once you have written your queries and generated your Rust code with Clorinde, it's time to use them. Hurray 🎉!
Let's say you have generated your Rust crate into ./clorinde
and added it to your Cargo.toml
, then this is as simple as importing the items you need from it, like so:
#![allow(unused)] fn main() { use clorinde::queries::authors; }
Building the query object
Building a query object starts with either the query function:
#![allow(unused)] fn main() { authors().bind(&client, Some("Greece")); }
or the generated parameter struct:
#![allow(unused)] fn main() { use clorinde::{ client::Params, queries::{authors, AuthorsParams} }; authors().params( &client, AuthorsParams { country: Some("Greece") } ); }
The query function is useful when you have a few obvious parameters, while the parameter struct is more explicit.
Note that in order to use the params
method, you need to import the clorinde::client::Params
trait.
Queries that don't have a return value (simple insertions, for example) don't generate a query object. Instead, when calling bind
or params
they execute and return the number of rows affected.
Query preparation
Clorinde provides two ways to execute queries, optimised for different use cases:
Default behaviour with bind()
The standard way to execute a query is to call bind()
directly:
#![allow(unused)] fn main() { authors().bind(&client, Some("Greece")).all().await?; }
This approach intelligently handles statement preparation:
- If your client supports statement caching (like
deadpool-postgres
), it will automatically cache-prepare the statement - If not, it executes without preparation to avoid unnecessary latency for one-off queries
This is the recommended approach for most applications, especially when using connection pools such as deadpool-postgres
.
Explicit preparation with prepare()
For queries that will be executed multiple times with different parameters, you can explicitly prepare the statement:
#![allow(unused)] fn main() { let prepared = authors().prepare(&client).await?; // Reuse the prepared statement multiple times for country in ["Greece", "Italy", "Spain"] { let results = prepared.bind(&client, Some(country)).all().await?; // Process results... } }
This provides the best performance when:
- You're executing the same query repeatedly in a loop
- You're not using a connection pool with built-in statement caching
- You want to ensure the statement is prepared once and reused
When using connection pools like deadpool-postgres
, the default bind()
behavior is usually sufficient as the pool handles statement caching automatically. Only use explicit prepare()
when you need to guarantee statement reuse within a specific scope.
Row mapping (optional)
Query objects have a map
method that allows them to transform the query's returned rows without requiring intermediate allocation. The following example is pretty contrived but illustrates how you can use this feature.
#![allow(unused)] fn main() { enum Country { Greece, TheRest } impl<'a> From<&'a str> for Country { fn from(s: &'a str) -> Self { if s == "Greece" { Self::Greece } else { Self::TheRest } } } struct CustomAuthor { full_name: String, country: Country, age: usize, } authors() .bind(&client) .map(|author| { let full_name = format!( "{}, {}", author.last_name.to_uppercase(), author.first_name ); let country = Country::from(author.country); CustomAuthor { full_name, country, age: author.age, } }); }
The result of a map is another query object.
Getting rows out of your queries
Once the query object has been built, use one of the following methods to select the expected number of rows:
opt
: one or zero rows (error otherwise).one
: exactly one row (error otherwise).iter
: iterator of zero or more rows.all
: likeiter
, but collects the rows in aVec
.
Here are some example uses:
#![allow(unused)] fn main() { author_by_id().bind(&client, &0).opt().await?; author_by_id().bind(&client, &0).one().await?; // Error if this author id doesn't exist authors().bind(&client).all().await?; authors().bind(&client).iter().await?.collect::<Vec<_>>(); // Acts the same as the previous line }