Merge pull request #1337 from diesel-rs/sg-queryable-embed

Allow embedded structs in `#[derive(QueryableByName)]`
This commit is contained in:
Sean Griffin 2017-12-04 06:02:10 -07:00 committed by GitHub
commit 18ad22bd3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 96 additions and 12 deletions

View File

@ -12,6 +12,10 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
table. If the `#[table_name]` annotation is left off, you must annotate each
field with `#[sql_type = "Integer"]`
* `#[derive(QueryableByName)]` can now handle embedding other structs. To have a
field whose type is a struct which implements `QueryableByName`, rather than a
single column in the query, add the annotation `#[diesel(embed)]`
### Changed
* `#[derive(QueryableByName)]` now requires that the table name be explicitly

View File

@ -47,6 +47,9 @@ where
/// you can annotate the field with `#[column_name = "some_column"]`. For tuple
/// structs, all fields must have this annotation.
///
/// If a field is another struct which implements `QueryableByName`, instead of
/// a column, you can annotate that struct with `#[diesel(embed)]`
///
/// [`sql_query`]: ../fn.sql_query.html
pub trait QueryableByName<DB>
where

View File

@ -8,6 +8,7 @@ pub struct Attr {
column_name: Option<syn::Ident>,
field_name: Option<syn::Ident>,
sql_type: Option<syn::Ty>,
flags: Vec<syn::Ident>,
pub ty: syn::Ty,
pub field_position: syn::Ident,
}
@ -22,12 +23,18 @@ impl Attr {
let sql_type = str_value_of_attr_with_name(&field.attrs, "sql_type").map(|st| {
syn::parse::ty(st).expect("#[sql_type] did not contain a valid Rust type")
});
let flags = list_value_of_attr_with_name(&field.attrs, "diesel")
.unwrap_or_else(Vec::new)
.into_iter()
.cloned()
.collect();
Attr {
column_name: column_name,
field_name: field_name,
sql_type: sql_type,
ty: ty,
column_name,
field_name,
sql_type,
ty,
flags,
field_position: index.to_string().into(),
}
}
@ -49,6 +56,14 @@ impl Attr {
self.sql_type.as_ref()
}
pub fn has_flag<T>(&self, flag: &T) -> bool
where
T: ?Sized,
syn::Ident: PartialEq<T>,
{
self.flags.iter().any(|f| f == flag)
}
fn field_kind(&self) -> &str {
if is_option_ty(&self.ty) {
"option"

View File

@ -43,7 +43,7 @@ pub fn derive_queryable(input: TokenStream) -> TokenStream {
expand_derive(input, queryable::derive_queryable)
}
#[proc_macro_derive(QueryableByName, attributes(table_name, column_name, sql_type))]
#[proc_macro_derive(QueryableByName, attributes(table_name, column_name, sql_type, diesel))]
pub fn derive_queryable_by_name(input: TokenStream) -> TokenStream {
expand_derive(input, queryable_by_name::derive)
}

View File

@ -15,10 +15,14 @@ pub fn derive(item: syn::DeriveInput) -> Tokens {
let attr_where_clause = model.attrs.iter().map(|attr| {
let attr_ty = &attr.ty;
let st = sql_type(attr, &model);
quote! {
__DB: diesel::types::HasSqlType<#st>,
#attr_ty: diesel::types::FromSql<#st, __DB>,
if attr.has_flag("embed") {
quote!(#attr_ty: diesel::query_source::QueryableByName<__DB>,)
} else {
let st = sql_type(attr, &model);
quote!(
__DB: diesel::types::HasSqlType<#st>,
#attr_ty: diesel::types::FromSql<#st, __DB>,
)
}
});
@ -45,9 +49,13 @@ pub fn derive(item: syn::DeriveInput) -> Tokens {
fn build_expr_for_model(model: &Model) -> Tokens {
let attr_exprs = model.attrs.iter().map(|attr| {
let name = attr.field_name();
let column_name = attr.column_name();
let st = sql_type(attr, model);
quote!(#name: diesel::row::NamedRow::get::<#st, _>(row, stringify!(#column_name))?)
if attr.has_flag("embed") {
quote!(#name: diesel::query_source::QueryableByName::build(row)?)
} else {
let column_name = attr.column_name();
let st = sql_type(attr, model);
quote!(#name: diesel::row::NamedRow::get::<#st, _>(row, stringify!(#column_name))?)
}
});
quote!(Self {

View File

@ -59,3 +59,57 @@ fn struct_with_no_table() {
let data = sql_query("SELECT 1 AS foo, 2 AS bar").get_result(&conn);
assert_eq!(Ok(MyStructNamedSoYouCantInferIt { foo: 1, bar: 2 }), data);
}
#[test]
fn embedded_struct() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, QueryableByName)]
#[table_name = "my_structs"]
struct A {
foo: IntRust,
#[diesel(embed)] b: B,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, QueryableByName)]
#[table_name = "my_structs"]
struct B {
bar: IntRust,
}
let conn = connection();
let data = sql_query("SELECT 1 AS foo, 2 AS bar").get_result(&conn);
assert_eq!(
Ok(A {
foo: 1,
b: B { bar: 2 },
}),
data
);
}
#[test]
fn embedded_option() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, QueryableByName)]
#[table_name = "my_structs"]
struct A {
foo: IntRust,
#[diesel(embed)] b: Option<B>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, QueryableByName)]
#[table_name = "my_structs"]
struct B {
bar: IntRust,
}
let conn = connection();
let data = sql_query("SELECT 1 AS foo, 2 AS bar").get_result(&conn);
assert_eq!(
Ok(A {
foo: 1,
b: Some(B { bar: 2 }),
}),
data
);
let data = sql_query("SELECT 1 AS foo, NULL AS bar").get_result(&conn);
assert_eq!(Ok(A { foo: 1, b: None }), data);
}