From 7b773fb712b77dd4ea950086f8296c7eaecb781d Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Wed, 27 Oct 2021 19:37:55 +0200 Subject: [PATCH 1/2] Support skipping fields in Queryable derive (falls back to Default impl) --- diesel_derives/src/field.rs | 18 ++++++++++---- diesel_derives/src/lib.rs | 3 +++ diesel_derives/src/queryable.rs | 22 +++++++++++------ diesel_derives/tests/queryable.rs | 40 +++++++++++++++++++++++++++---- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/diesel_derives/src/field.rs b/diesel_derives/src/field.rs index 9bfe979f1b6c..d90cbe063839 100644 --- a/diesel_derives/src/field.rs +++ b/diesel_derives/src/field.rs @@ -100,11 +100,19 @@ impl Field { } } - pub fn ty_for_deserialize(&self) -> Result, Diagnostic> { - if let Some(meta) = self.flags.nested_item("deserialize_as")? { - meta.ty_value().map(Cow::Owned) - } else { - Ok(Cow::Borrowed(&self.ty)) + /// Returns None if has `skip` flag + pub fn ty_for_deserialize(&self) -> Result>, Diagnostic> { + match ( + self.flags.nested_item("deserialize_as")?, + self.has_flag("skip"), + ) { + (Some(meta), false) => Ok(Some(Cow::Owned(meta.ty_value()?))), + (None, false) => Ok(Some(Cow::Borrowed(&self.ty))), + (None, true) => Ok(None), + (Some(_), true) => Err(self + .flags + .span() + .error("Cannot have both `deserialize_as` and `skip` attributes")), } } } diff --git a/diesel_derives/src/lib.rs b/diesel_derives/src/lib.rs index 0fabb10a5f05..5758f451f42c 100644 --- a/diesel_derives/src/lib.rs +++ b/diesel_derives/src/lib.rs @@ -413,6 +413,9 @@ pub fn derive_query_id(input: TokenStream) -> TokenStream { /// Then `Type` is converted via /// [`.try_into`](https://doc.rust-lang.org/stable/std/convert/trait.TryInto.html#tymethod.try_into) /// into the field type. By default this derive will deserialize directly into the field type +/// * `[diesel(skip)]`, instead of deserializing anything into that type, deserialization will +/// behave as if this field wasn't here, and it will simply be set using the `Default` impl +/// of the type of the field. /// /// # Examples /// diff --git a/diesel_derives/src/queryable.rs b/diesel_derives/src/queryable.rs index 058199765754..a91edc21e1d7 100644 --- a/diesel_derives/src/queryable.rs +++ b/diesel_derives/src/queryable.rs @@ -12,14 +12,22 @@ pub fn derive(item: syn::DeriveInput) -> Result, _>>()?; let field_ty = &field_ty; - let build_expr = model.fields().iter().enumerate().map(|(i, f)| { - let i = syn::Index::from(i); - f.name.assign(parse_quote!(row.#i.try_into()?)) - }); - let sql_type = (0..model.fields().len()) + let build_expr = { + let mut non_default_field_idx = 0; + model.fields().iter().map(move |f| { + f.name.assign(if f.has_flag("skip") { + parse_quote!(std::default::Default::default()) + } else { + let i = syn::Index::from(non_default_field_idx); + non_default_field_idx += 1; + parse_quote!(row.#i.try_into()?) + }) + }) + }; + let sql_type = (0..field_ty.len()) .map(|i| { let i = syn::Ident::new(&format!("__ST{}", i), proc_macro2::Span::call_site()); quote!(#i) @@ -32,7 +40,7 @@ pub fn derive(item: syn::DeriveInput) -> Result("1, 2")).get_result(conn); assert_eq!(Ok(MyStruct(1, 2)), data); } + +#[test] +fn named_struct_definition_with_skip() { + #[derive(Debug, PartialEq, Eq, Queryable)] + struct MyStruct { + foo: i32, + #[diesel(skip)] + should_be_default: Vec, + bar: String, + } + + let conn = &mut connection(); + let data = select(sql::<(Integer, Text)>("1, '2'")).get_result(conn); + assert_eq!( + Ok(MyStruct { + foo: 1, + should_be_default: Vec::default(), + bar: "2".to_string(), + }), + data + ); +} + +#[test] +fn tuple_struct_with_skip() { + #[derive(Debug, PartialEq, Eq, Queryable)] + struct MyStruct(i32, #[diesel(skip)] Option, String); + + let conn = &mut connection(); + let data = select(sql::<(Integer, Text)>("1, '2'")).get_result(conn); + assert_eq!(Ok(MyStruct(1, None, "2".to_string())), data); +} From fe840947f3f9e946775e2ee617c35e7f6f0993c5 Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Wed, 27 Oct 2021 20:03:03 +0200 Subject: [PATCH 2/2] skip on Selectable --- diesel_derives/src/field.rs | 6 +++++- diesel_derives/src/lib.rs | 1 + diesel_derives/src/selectable.rs | 9 +++++++-- diesel_derives/tests/selectable.rs | 17 +++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/diesel_derives/src/field.rs b/diesel_derives/src/field.rs index d90cbe063839..62ad8e2aec7a 100644 --- a/diesel_derives/src/field.rs +++ b/diesel_derives/src/field.rs @@ -91,6 +91,10 @@ impl Field { self.flags.has_flag(flag) } + pub fn has_skip_flag(&self) -> bool { + self.has_flag("skip") + } + pub fn ty_for_serialize(&self) -> Result, Diagnostic> { if let Some(meta) = self.flags.nested_item("serialize_as")? { let ty = meta.ty_value()?; @@ -104,7 +108,7 @@ impl Field { pub fn ty_for_deserialize(&self) -> Result>, Diagnostic> { match ( self.flags.nested_item("deserialize_as")?, - self.has_flag("skip"), + self.has_skip_flag(), ) { (Some(meta), false) => Ok(Some(Cow::Owned(meta.ty_value()?))), (None, false) => Ok(Some(Cow::Borrowed(&self.ty))), diff --git a/diesel_derives/src/lib.rs b/diesel_derives/src/lib.rs index 5758f451f42c..da4446d56e8f 100644 --- a/diesel_derives/src/lib.rs +++ b/diesel_derives/src/lib.rs @@ -775,6 +775,7 @@ pub fn derive_queryable_by_name(input: TokenStream) -> TokenStream { /// * `#[diesel(embed)]`, specifies that the current field maps not only /// single database column, but is a type that implements /// `Selectable` on it's own +/// * `[diesel(skip)]`, field is ignored for the `Selectable` impl. Consistent with `Queryable`. #[proc_macro_derive(Selectable, attributes(table_name, column_name, sql_type, diesel))] pub fn derive_selectable(input: TokenStream) -> TokenStream { expand_proc_macro(input, selectable::derive) diff --git a/diesel_derives/src/selectable.rs b/diesel_derives/src/selectable.rs index 1f6b2446c77d..2ec4a5fff34a 100644 --- a/diesel_derives/src/selectable.rs +++ b/diesel_derives/src/selectable.rs @@ -17,9 +17,14 @@ pub fn derive(item: syn::DeriveInput) -> Result>(); - let field_columns_inst = model.fields().iter().map(|f| field_column_inst(f, &model)); + let field_columns_ty = fields.iter().map(|f| field_column_ty(f, &model)); + let field_columns_inst = fields.iter().map(|f| field_column_inst(f, &model)); Ok(wrap_in_dummy_mod(quote! { use diesel::expression::Selectable; diff --git a/diesel_derives/tests/selectable.rs b/diesel_derives/tests/selectable.rs index 81d959a9efe9..3a5abb311433 100644 --- a/diesel_derives/tests/selectable.rs +++ b/diesel_derives/tests/selectable.rs @@ -81,3 +81,20 @@ fn embedded_option() { let data = my_structs::table.select(A::as_select()).get_result(conn); assert!(data.is_err()); } + +#[test] +fn named_struct_definition_with_skip() { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Queryable, Selectable)] + #[table_name = "my_structs"] + struct MyStruct { + foo: i32, + #[diesel(skip)] + bar: i32, + } + + let conn = &mut connection(); + let data = my_structs::table + .select(MyStruct::as_select()) + .get_result(conn); + assert!(data.is_err()); +}