mirror of
https://github.com/diesel-rs/diesel.git
synced 2024-10-04 09:39:24 +03:00
Merge pull request #1337 from diesel-rs/sg-queryable-embed
Allow embedded structs in `#[derive(QueryableByName)]`
This commit is contained in:
commit
18ad22bd3a
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user