Helper macros and traits built around tokio-postgres to define queries with human readable parameters and return values.
Example
# use Client;
# use ;
#
# async
Queries
The preferred way of constructing a new Query
is through the query!
macro. It uses a
syntax similar to the format!(...)
family of macros from the standard library. The first
parameter is the SQL query and is always given as a string literal (this might be relaxed in the
future). This string literal may contain parameter bindings on the form $ident
where ident
is any valid Rust identifier ($abc
, $value_123
, etc.).
# use query;
let age = 42;
let insert_person = query!;
During compilation the query is converted into the format expected by PostgreSQL: parameter bindings are converted to using numbers ($1, $2, etc.) and the actual parameter values are put into a 1-indexed array. The code snippet above would be expanded into the following:
# use *;
let age = 42;
let insert_person = new_static;
Dynamic Queries
If necessary, queries may be constructed from &str
s at runtime instead of the usual
compile-time string literals expected by the query!
macro. This is achieved by using the
query_dyn!
macro instead. In addition to dynamic queries, parameter bindings may also be
dynamically:
# use *;
let mut sql = "SELECT * FROM people WHERE name = $name".to_string;
let mut bindings = Vec new;
// Add a filter at runtime
sql += " AND age > $min_age";
bindings.push;
let query: = query_dyn!;
Using dynamic queries does introduce some errors that cannot be caught at runtime: such as some
parameters in the query not having a matching binding. Because of this the value returned by the
query_dyn!
macro is not a Query
but a Result<Query>
which carries an error you must
handle:
# use *;
let mut sql = "SELECT * FROM people".to_string;
sql += " WHERE age <= $max_age AND name = $name";
let query: = query_dyn!;
assert!;
Data Extraction
In addition to helping you define new queries this crate provides the FromSqlRow
trait which
makes it easy to extract typed values from the resulting rows. The easiest way to implement this
trait for new struct
s is to use the included derive(FromSqlRow)
macro.
- If used on a tuple struct, values will be extracted from the corresponding columns based on their position in the tuple.
- If used on a stuct with named fields, values will be extracted from the column with the same name as the field.
# use *;
;
;
Multi-mapping
If you query the same table multiple times it gets tedious to have to redefine structs with the same fields over and over. Preferably we would like to reuse the same definition multiple times. We can do this be utilizing "multi-mapping".
Partitions
Multi-mapping works by splitting the columns of rows returned by a query into multiple
partitions (or slices). For example, if we had the query SELECT books.*, authors.* FROM ...
,
we would like to extract the data into two structs: Book
and Author
. We accomplish this by
looking at the columns returned by the database and splitting them into partitions:
Columns: id, title, release_date, genre, id, name, birthyear
Partitions: +------------Book-------------+ +------Author-----+
Partitioning schemes
There are two supported ways to partition a row: either we specify the number of columns
required to populate each struct (in the example above: 4 columns for Book and 3 for author), or
we split on the name of a column. The former should generally only be used when you know the
number of columns isn't going to change. The latter is less prone to break provided you choose
an appropriate column to split on (a good candidate is usually id
as almost all tables have
this as their first
column).
You choose which partitioning scheme you want to use by using the provided
attributes. In order to accomplish the partitioning in
the example above we could split on the column name id
:
# use FromSqlRow;
Alternatively, we can make Author
a part of the Book
struct:
# use FromSqlRow;
Many-to-one Relationships
In the previous examples we had a Book
that contained an Author
. This is what is called a
many-to-one relationship, since one book only has one author, but many books may share the same
author (or so we assume anyway). What if you instead had Author
an author that contained many
Book
s? We know that one author may write many books, so that is a one-to-many relationship. We
can write an extractor for that case as well:
# use *;
# use Client;
# async
See the section on attributes for a more advanced in-depth explanation of multi-mapping.
Caching queries
From time to time you probably want to execute the same query multiple times, but with different
parameters. In times like these we can decrease the load on the database by preparing our
queries before executing them. By wrapping a client in a Caching
struct this behaviour is
automatically provided for all queries that originate from this crate:
# use Client;
# use ;
#
# async