diesel_derives/
as_changeset.rs
1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::spanned::Spanned as _;
4use syn::{parse_quote, DeriveInput, Expr, Path, Result, Type};
5
6use crate::attrs::AttributeSpanWrapper;
7use crate::field::Field;
8use crate::model::Model;
9use crate::util::{inner_of_option_ty, is_option_ty, wrap_in_dummy_mod};
10
11pub fn derive(item: DeriveInput) -> Result<TokenStream> {
12 let model = Model::from_item(&item, false, false)?;
13
14 let struct_name = &item.ident;
15 let table_name = &model.table_names()[0];
16
17 let fields_for_update = model
18 .fields()
19 .iter()
20 .filter(|f| {
21 !model
22 .primary_key_names
23 .iter()
24 .any(|p| f.column_name().map(|f| f == *p).unwrap_or_default())
25 })
26 .collect::<Vec<_>>();
27
28 if fields_for_update.is_empty() {
29 return Err(syn::Error::new(
30 proc_macro2::Span::call_site(),
31 "Deriving `AsChangeset` on a structure that only contains primary keys isn't supported.\n\
32 help: If you want to change the primary key of a row, you should do so with `.set(table::id.eq(new_id))`.\n\
33 note: `#[derive(AsChangeset)]` never changes the primary key of a row."
34 ));
35 }
36
37 let treat_none_as_null = model.treat_none_as_null();
38
39 let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
40
41 let mut generate_borrowed_changeset = true;
42
43 let mut direct_field_ty = Vec::with_capacity(fields_for_update.len());
44 let mut direct_field_assign = Vec::with_capacity(fields_for_update.len());
45 let mut ref_field_ty = Vec::with_capacity(fields_for_update.len());
46 let mut ref_field_assign = Vec::with_capacity(fields_for_update.len());
47
48 for field in fields_for_update {
49 let treat_none_as_null = match &field.treat_none_as_null {
51 Some(attr) => {
52 if !is_option_ty(&field.ty) {
53 return Err(syn::Error::new(
54 field.ty.span(),
55 "expected `treat_none_as_null` field to be of type `Option<_>`",
56 ));
57 }
58
59 attr.item
60 }
61 None => treat_none_as_null,
62 };
63
64 match field.serialize_as.as_ref() {
65 Some(AttributeSpanWrapper { item: ty, .. }) => {
66 direct_field_ty.push(field_changeset_ty_serialize_as(
67 field,
68 table_name,
69 ty,
70 treat_none_as_null,
71 )?);
72 direct_field_assign.push(field_changeset_expr_serialize_as(
73 field,
74 table_name,
75 ty,
76 treat_none_as_null,
77 )?);
78
79 generate_borrowed_changeset = false; }
81 None => {
82 direct_field_ty.push(field_changeset_ty(
83 field,
84 table_name,
85 None,
86 treat_none_as_null,
87 )?);
88 direct_field_assign.push(field_changeset_expr(
89 field,
90 table_name,
91 None,
92 treat_none_as_null,
93 )?);
94 ref_field_ty.push(field_changeset_ty(
95 field,
96 table_name,
97 Some(quote!(&'update)),
98 treat_none_as_null,
99 )?);
100 ref_field_assign.push(field_changeset_expr(
101 field,
102 table_name,
103 Some(quote!(&)),
104 treat_none_as_null,
105 )?);
106 }
107 }
108 }
109
110 let changeset_owned = quote! {
111 impl #impl_generics AsChangeset for #struct_name #ty_generics
112 #where_clause
113 {
114 type Target = #table_name::table;
115 type Changeset = <(#(#direct_field_ty,)*) as AsChangeset>::Changeset;
116
117 fn as_changeset(self) -> Self::Changeset {
118 (#(#direct_field_assign,)*).as_changeset()
119 }
120 }
121 };
122
123 let changeset_borrowed = if generate_borrowed_changeset {
124 let mut impl_generics = item.generics.clone();
125 impl_generics.params.push(parse_quote!('update));
126 let (impl_generics, _, _) = impl_generics.split_for_impl();
127
128 quote! {
129 impl #impl_generics AsChangeset for &'update #struct_name #ty_generics
130 #where_clause
131 {
132 type Target = #table_name::table;
133 type Changeset = <(#(#ref_field_ty,)*) as AsChangeset>::Changeset;
134
135 fn as_changeset(self) -> Self::Changeset {
136 (#(#ref_field_assign,)*).as_changeset()
137 }
138 }
139 }
140 } else {
141 quote! {}
142 };
143
144 Ok(wrap_in_dummy_mod(quote!(
145 use diesel::query_builder::AsChangeset;
146 use diesel::prelude::*;
147
148 #changeset_owned
149
150 #changeset_borrowed
151 )))
152}
153
154fn field_changeset_ty(
155 field: &Field,
156 table_name: &Path,
157 lifetime: Option<TokenStream>,
158 treat_none_as_null: bool,
159) -> Result<TokenStream> {
160 let column_name = field.column_name()?.to_ident()?;
161 if !treat_none_as_null && is_option_ty(&field.ty) {
162 let field_ty = inner_of_option_ty(&field.ty);
163 Ok(
164 quote!(std::option::Option<diesel::dsl::Eq<#table_name::#column_name, #lifetime #field_ty>>),
165 )
166 } else {
167 let field_ty = &field.ty;
168 Ok(quote!(diesel::dsl::Eq<#table_name::#column_name, #lifetime #field_ty>))
169 }
170}
171
172fn field_changeset_expr(
173 field: &Field,
174 table_name: &Path,
175 lifetime: Option<TokenStream>,
176 treat_none_as_null: bool,
177) -> Result<TokenStream> {
178 let field_name = &field.name;
179 let column_name = field.column_name()?.to_ident()?;
180 if !treat_none_as_null && is_option_ty(&field.ty) {
181 if lifetime.is_some() {
182 Ok(quote!(self.#field_name.as_ref().map(|x| #table_name::#column_name.eq(x))))
183 } else {
184 Ok(quote!(self.#field_name.map(|x| #table_name::#column_name.eq(x))))
185 }
186 } else {
187 Ok(quote!(#table_name::#column_name.eq(#lifetime self.#field_name)))
188 }
189}
190
191fn field_changeset_ty_serialize_as(
192 field: &Field,
193 table_name: &Path,
194 ty: &Type,
195 treat_none_as_null: bool,
196) -> Result<TokenStream> {
197 let column_name = field.column_name()?.to_ident()?;
198 if !treat_none_as_null && is_option_ty(&field.ty) {
199 let inner_ty = inner_of_option_ty(ty);
200 Ok(quote!(std::option::Option<diesel::dsl::Eq<#table_name::#column_name, #inner_ty>>))
201 } else {
202 Ok(quote!(diesel::dsl::Eq<#table_name::#column_name, #ty>))
203 }
204}
205
206fn field_changeset_expr_serialize_as(
207 field: &Field,
208 table_name: &Path,
209 ty: &Type,
210 treat_none_as_null: bool,
211) -> Result<TokenStream> {
212 let field_name = &field.name;
213 let column_name = field.column_name()?.to_ident()?;
214 let column: Expr = parse_quote!(#table_name::#column_name);
215 if !treat_none_as_null && is_option_ty(&field.ty) {
216 Ok(quote!(self.#field_name.map(|x| #column.eq(::std::convert::Into::<#ty>::into(x)))))
217 } else {
218 Ok(quote!(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name))))
219 }
220}