Skip to content

Commit

Permalink
Support #[diesel(embed)] on Insertable structs.
Browse files Browse the repository at this point in the history
There have been a few people asking about how to implement `Insertable`
for enums which map to more than one column. This would allow those
people to only have to implement `Insertable` for that enum, as opposed
to the type which contains it.
  • Loading branch information
sgrif committed Feb 6, 2018
1 parent f3c786b commit 2fe3f52
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
replacing it. This is useful with boxed queries to dynamically construct an
order by clause containing an unknown number of columns.

* `#[derive(Insertable)]` can now work on structs with fields that implement
`Insertable` (meaning one field can map to more than one column). Add
`#[diesel(embed)]` to the field to enable this behavior.

### Changed

* The bounds on `impl ToSql for Cow<'a, T>` have been loosened to no longer
Expand Down
5 changes: 5 additions & 0 deletions diesel/src/insertable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ use sqlite::Sqlite;
/// with `#[table_name = "some_table_name"]`. If the field name of your
/// struct differs from the name of the column, you can annotate the field
/// with `#[column_name = "some_column_name"]`.
///
/// Your struct can also contain fields which implement `Insertable`. This is
/// useful when you want to have one field map to more than one column (for
/// example, an enum that maps to a label and a value column). Add
/// `#[diesel(embed)]` to any such fields.
pub trait Insertable<T> {
/// The `VALUES` clause to insert these records
///
Expand Down
35 changes: 22 additions & 13 deletions diesel_derives/src/insertable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,32 @@ pub fn derive(item: syn::DeriveInput) -> Result<quote::Tokens, Diagnostic> {
}

fn field_ty(field: &Field, table_name: syn::Ident) -> syn::Type {
let inner_ty = inner_of_option_ty(&field.ty);
let column_name = field.column_name();
parse_quote!(
std::option::Option<diesel::dsl::Eq<
#table_name::#column_name,
&'insert #inner_ty,
>>
)
if field.has_flag("embed") {
let field_ty = &field.ty;
parse_quote!(&'insert #field_ty)
} else {
let inner_ty = inner_of_option_ty(&field.ty);
let column_name = field.column_name();
parse_quote!(
std::option::Option<diesel::dsl::Eq<
#table_name::#column_name,
&'insert #inner_ty,
>>
)
}
}

fn field_expr(field: &Field, table_name: syn::Ident) -> syn::Expr {
let field_access = field.name.access();
let column_name = field.column_name();
let column: syn::Expr = parse_quote!(#table_name::#column_name);
if is_option_ty(&field.ty) {
parse_quote!(self#field_access.as_ref().map(|x| #column.eq(x)))
if field.has_flag("embed") {
parse_quote!(&self#field_access)
} else {
parse_quote!(std::option::Option::Some(#column.eq(&self#field_access)))
let column_name = field.column_name();
let column: syn::Expr = parse_quote!(#table_name::#column_name);
if is_option_ty(&field.ty) {
parse_quote!(self#field_access.as_ref().map(|x| #column.eq(x)))
} else {
parse_quote!(std::option::Option::Some(#column.eq(&self#field_access)))
}
}
}
10 changes: 5 additions & 5 deletions diesel_derives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ pub fn derive_identifiable(input: TokenStream) -> TokenStream {
expand_derive(input, identifiable::derive)
}

#[proc_macro_derive(Insertable, attributes(table_name, column_name, diesel))]
pub fn derive_insertable(input: TokenStream) -> TokenStream {
expand_derive(input, insertable::derive)
}

#[proc_macro_derive(QueryId)]
pub fn derive_query_id(input: TokenStream) -> TokenStream {
expand_derive(input, query_id::derive)
}

#[proc_macro_derive(Insertable, attributes(table_name, column_name))]
pub fn derive_insertable(input: TokenStream) -> TokenStream {
expand_derive(input, insertable::derive)
}

#[proc_macro_derive(Queryable, attributes(column_name))]
pub fn derive_queryable(input: TokenStream) -> TokenStream {
expand_derive(input, queryable::derive)
Expand Down
34 changes: 34 additions & 0 deletions diesel_derives/tests/insertable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,37 @@ fn insertable_with_slice_of_borrowed() {
let expected = vec![vec![String::from("hi"), String::from("there")]];
assert_eq!(Ok(expected), saved);
}

#[test]
fn embedded_struct() {
#[derive(Insertable)]
#[table_name = "users"]
struct NameAndHairColor<'a> {
name: &'a str,
hair_color: &'a str,
}

#[derive(Insertable)]
struct User<'a> {
id: i32,
#[diesel(embed)]
name_and_hair_color: NameAndHairColor<'a>,
}

let conn = connection();
let new_user = User {
id: 1,
name_and_hair_color: NameAndHairColor {
name: "Sean",
hair_color: "Black",
},
};
insert_into(users::table)
.values(&new_user)
.execute(&conn)
.unwrap();

let saved = users::table.load::<(i32, String, Option<String>)>(&conn);
let expected = vec![(1, "Sean".to_string(), Some("Black".to_string()))];
assert_eq!(Ok(expected), saved);
}

0 comments on commit 2fe3f52

Please sign in to comment.