Skip to content

Commit

Permalink
Add #[derive(FromRow)] for tuple struct
Browse files Browse the repository at this point in the history
  • Loading branch information
dvermd authored and abonander committed Oct 26, 2020
1 parent e816943 commit 964837e
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 6 deletions.
61 changes: 55 additions & 6 deletions sqlx-macros/src/derives/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use proc_macro2::Span;
use quote::quote;
use syn::{
parse_quote, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field,
Fields, FieldsNamed, Lifetime, Stmt,
Fields, FieldsNamed, FieldsUnnamed, Lifetime, Stmt,
};

use super::attributes::parse_child_attributes;
Expand All @@ -15,12 +15,9 @@ pub fn expand_derive_from_row(input: &DeriveInput) -> syn::Result<proc_macro2::T
}) => expand_derive_from_row_struct(input, named),

Data::Struct(DataStruct {
fields: Fields::Unnamed(_),
fields: Fields::Unnamed(FieldsUnnamed { unnamed, .. }),
..
}) => Err(syn::Error::new_spanned(
input,
"tuple structs are not supported",
)),
}) => expand_derive_from_row_struct_unnamed(input, unnamed),

Data::Struct(DataStruct {
fields: Fields::Unit,
Expand Down Expand Up @@ -111,3 +108,55 @@ fn expand_derive_from_row_struct(
}
))
}

fn expand_derive_from_row_struct_unnamed(
input: &DeriveInput,
fields: &Punctuated<Field, Comma>,
) -> syn::Result<proc_macro2::TokenStream> {
let ident = &input.ident;

let generics = &input.generics;

let (lifetime, provided) = generics
.lifetimes()
.next()
.map(|def| (def.lifetime.clone(), false))
.unwrap_or_else(|| (Lifetime::new("'a", Span::call_site()), true));

let (_, ty_generics, _) = generics.split_for_impl();

let mut generics = generics.clone();
generics.params.insert(0, parse_quote!(R: sqlx::Row));

if provided {
generics.params.insert(0, parse_quote!(#lifetime));
}

let predicates = &mut generics.make_where_clause().predicates;

predicates.push(parse_quote!(usize: sqlx::ColumnIndex<R>));

for field in fields {
let ty = &field.ty;

predicates.push(parse_quote!(#ty: sqlx::decode::Decode<#lifetime, R::Database>));
predicates.push(parse_quote!(#ty: sqlx::types::Type<R::Database>));
}

let (impl_generics, _, where_clause) = generics.split_for_impl();

let gets = fields
.iter()
.enumerate()
.map(|(idx, _)| quote!(row.try_get(#idx)?));

Ok(quote!(
impl #impl_generics sqlx::FromRow<#lifetime, R> for #ident #ty_generics #where_clause {
fn from_row(row: &#lifetime R) -> sqlx::Result<Self> {
Ok(#ident (
#(#gets),*
))
}
}
))
}
38 changes: 38 additions & 0 deletions tests/postgres/derives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,44 @@ async fn test_from_row_with_rename() -> anyhow::Result<()> {
Ok(())
}

#[cfg(feature = "macros")]
#[sqlx_macros::test]
async fn test_from_row_tuple() -> anyhow::Result<()> {
let mut conn = new::<Postgres>().await?;

#[derive(Debug, sqlx::FromRow)]
struct Account(i32, String);

let account: Account = sqlx::query_as(
"SELECT * from (VALUES (1, 'Herp Derpinson')) accounts(id, name) where id = $1",
)
.bind(1_i32)
.fetch_one(&mut conn)
.await?;

assert_eq!(account.0, 1);
assert_eq!(account.1, "Herp Derpinson");

// A _single_ lifetime may be used but only when using the lowest-level API currently (Query::fetch)

#[derive(sqlx::FromRow)]
struct RefAccount<'a>(i32, &'a str);

let mut cursor = sqlx::query(
"SELECT * from (VALUES (1, 'Herp Derpinson')) accounts(id, name) where id = $1",
)
.bind(1_i32)
.fetch(&mut conn);

let row = cursor.try_next().await?.unwrap();
let account = RefAccount::from_row(&row)?;

assert_eq!(account.0, 1);
assert_eq!(account.1, "Herp Derpinson");

Ok(())
}

#[cfg(feature = "macros")]
#[sqlx_macros::test]
async fn test_default() -> anyhow::Result<()> {
Expand Down

0 comments on commit 964837e

Please sign in to comment.