[−][src]Derive Macro postgres_query::FromSqlRow
#[derive(FromSqlRow)] { // Attributes available to this derive: #[row] }
Extract values from a row.
- 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.
Example
#[derive(FromSqlRow)] struct TupleData(i32, String); #[derive(FromSqlRow)] struct NamedData { age: i32, name: String, };
Attributes
Data extraction can be customized by using the #[row(...)]
attribute. Attributes can be
separated into two categories, those which go on the container itself:
and those which are placed on the container's fields:
#[row(rename = "...")]
#[row(flatten)]
#[row(stride = N)]
#[row(split = "...")]
#[row(key)]
#[row(merge)]
Container attributes
These attributes are put on the struct itself.
#[row(exact)]
Partition the row according to the number of columns matched by each group.
Note that no order is forced upon fields within any group. In the example below, that means that
even though the generation
and origin
fields are flipped relative to the query, the
extraction will be successful:
#[derive(FromSqlRow)] #[row(exact)] struct Family { generation: i32, origin: String, #[row(flatten)] parent: Person, #[row(flatten)] child: Person, } #[derive(FromSqlRow)] struct Person { id: i32, name: String, } let family = query!( "SELECT 'Germany' as origin, 7 as generation, 1 as id, 'Bob' as name, 2 as id, 'Ike' as name" ) .fetch_one::<Family, _>(&client) .await?;
#[row(split)]
Partition the row according to the field's split points.
Split points are introduced by using the #[row(split = "...")]
attribute on
fields.
#[derive(FromSqlRow)] #[row(split)] struct Family { generation: i32, origin: String, #[row(flatten, split = "id")] parent: Person, #[row(flatten, split = "id")] child: Person, } #[derive(FromSqlRow)] struct Person { id: i32, name: String, } let family = query!( "SELECT 'Germany' as origin, 7 as generation, 1 as id, 'Bob' as name, 2 as id, 'Ike' as name" ) .fetch_one::<Family, _>(&client) .await?;
#[row(group)]
Enables one-to-many mapping for the container. One-to-many mapping requires that at least one
field has the #[row(key)]
attribute and that one other field has the #[row(merge)]
attribute.
When extracting values from multiple rows, any two adjacent rows that are identical on their
fields marked with #[row(key)]
will have their fields tagged with #[row(merge)]
merged. This
means that in order to get the expected relation back, you may need to include a GROUP BY
statement in your SQL query, hence the name group
.
#[derive(Debug, FromSqlRow)] #[row(group)] struct Author { #[row(key)] name: String, #[row(merge)] books: Vec<Book>, } #[derive(Debug, FromSqlRow)] struct Book { title: String, } let authors = query!( "SELECT 'J.R.R. Tolkien' as name, 'The Fellowship of the Ring' as title UNION ALL SELECT 'J.R.R. Tolkien', 'The Two Towers' UNION ALL SELECT 'Andrzej Sapkowski', 'The Last Wish' UNION ALL SELECT 'J.R.R. Tolkien', 'Return of the King'") .fetch::<Author, _>(&client) .await?; assert_eq!(authors[0].name, "J.R.R. Tolkien"); assert_eq!(authors[0].books[0].title, "The Fellowship of the Ring"); assert_eq!(authors[0].books[1].title, "The Two Towers"); assert_eq!(authors[1].name, "Andrzej Sapkowski"); assert_eq!(authors[1].books[0].title, "The Last Wish"); assert_eq!(authors[2].name, "J.R.R. Tolkien"); assert_eq!(authors[2].books[0].title, "Return of the King");
#[row(hash)]
Like #[row(group)]
, but all previous rows are considered when merging. This is accomplished by
using a HashMap
, hence the name. This implies that all keys have to implement the Hash
and
Eq
traits:
#[derive(Debug, FromSqlRow)] #[row(hash)] struct Author { #[row(key)] name: String, #[row(merge)] books: Vec<Book>, } #[derive(Debug, FromSqlRow)] struct Book { title: String, } let authors = query!( "SELECT 'J.R.R. Tolkien' as name, 'The Fellowship of the Ring' as title UNION ALL SELECT 'J.R.R. Tolkien', 'The Two Towers' UNION ALL SELECT 'Andrzej Sapkowski', 'The Last Wish' UNION ALL SELECT 'J.R.R. Tolkien', 'Return of the King'") .fetch::<Author, _>(&client) .await?; assert_eq!(authors[0].name, "J.R.R. Tolkien"); assert_eq!(authors[0].books[0].title, "The Fellowship of the Ring"); assert_eq!(authors[0].books[1].title, "The Two Towers"); assert_eq!(authors[0].books[2].title, "Return of the King"); assert_eq!(authors[1].name, "Andrzej Sapkowski"); assert_eq!(authors[1].books[0].title, "The Last Wish");
Field attributes
These attributes are put on the fields of a container.
#[row(rename = "...")]
Use a name other than that of the field when looking up the name of the column.
#[derive(FromSqlRow)] struct Person { age: i32, // matches the column named "first_name" instead of "name" #[row(rename = "first_name")] name: String, }
#[row(flatten)]
Flatten the contents of this field into its container by recursively calling FromSqlRow
on the
field's type. This removes one level of nesting:
#[derive(FromSqlRow)] struct Customer { id: i32, #[row(flatten)] info: Person, } #[derive(FromSqlRow)] struct Person { name: String, age: i32 } let customer: Customer = query!("SELECT 14 as id, 'Bob' as name, 47 as age") .fetch_one(&client) .await?; assert_eq!(customer.id, 14); assert_eq!(customer.info.name, "Bob"); assert_eq!(customer.info.age, 47);
#[row(stride = N)]
Puts this field into a partition with exactly N
columns. Only available when using the
#[row(exact)]
attribute on the container,
#[derive(Debug, FromSqlRow)] struct Person { id: i32, name: String, } #[derive(Debug, FromSqlRow)] #[row(exact)] struct Family { // Matches first 4 columns #[row(flatten, stride = 4)] parent: Person, // Matches last 3 columns #[row(flatten, stride = 3)] child: Person, } let family = query!( "SELECT 11 as generation, 1 as id, 'Bob' as name, 42 as age, 2 as id, 'Ike' as name, 14 as age" ) .fetch_one::<Family, _>(&client) .await?; assert_eq!(family.parent.id, 1); assert_eq!(family.parent.name, "Bob"); assert_eq!(family.child.id, 2); assert_eq!(family.child.name, "Ike");
#[row(split = "...")]
Introduce an additional split right
before this field. Requires that the container has the split
attribute as well.
Intuitively this splits the row in two parts: every field before this attribute matches the columns before the split and every field afterwards matches the second remaining columns.
#[derive(FromSqlRow)] #[row(split)] struct User { // `id` and `name` will only match the columns before `email` id: i32, name: String, #[row(split = "email")] // `email`, `address` and `shoe_size` will only // match the columns after and including `email` email: String, address: String, shoe_size: i32, }
Note that the first split always matches first occurence of that column. This can result in some subtle bugs:
#[derive(FromSqlRow)] #[row(split)] struct Family { #[row(flatten)] parent: Person, #[row(flatten, split = "id")] child: Person, } #[derive(FromSqlRow)] struct Person { name: String, age: i32 } let query = query!("SELECT parent.*, child.* FROM ..."); // Imagine the query above results in the following columns: // // Columns: id, name, id, name // Splits: | // Partitions: +-parent-+ +-----child------+
The split causes parent
to match against all columns before the first id
, ie. an empty
partition. This would cause an error when executing the query.
A correct split would look like this:
#[derive(FromSqlRow)] #[row(split)] struct Family { #[row(flatten, split = "id")] parent: Person, #[row(flatten, split = "id")] child: Person, }
#[row(key)]
Specifies this field to be a key
field. key
fields are compared against each other when
extracting values from multiple rows. Rows are merged if the key fields in each row are
identical. You may have multiple key
fields within a single container, but none of them may
have the #[row(merge)]
attribute. Multiple key
fields will be treated as a tuple in
comparisons.
#[row(merge)]
Specifies this field to be a merge
field. This requires that the field's type implements the
Merge
trait. When two rows have been deemed to be equal based on the key
fields, the
corresponding merge
fields in those rows will be merged. You may specify multiple merge
fields within one container, but none of them may have the #[row(key)]
attribute.