rquery_orm_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Fields, Lit, Meta, NestedMeta};
4
5#[proc_macro_derive(Entity, attributes(table, column, key, relation))]
6pub fn entity(input: TokenStream) -> TokenStream {
7    let input = parse_macro_input!(input as DeriveInput);
8    let struct_name = input.ident;
9
10    // table attributes
11    let mut table_name = struct_name.to_string();
12    let mut table_schema: Option<String> = None;
13    for attr in &input.attrs {
14        if attr.path.is_ident("table") {
15            if let Ok(Meta::List(list)) = attr.parse_meta() {
16                for nested in list.nested.iter() {
17                    match nested {
18                        NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("name") => {
19                            if let Lit::Str(s) = &nv.lit {
20                                table_name = s.value();
21                            }
22                        }
23                        NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("schema") => {
24                            if let Lit::Str(s) = &nv.lit {
25                                table_schema = Some(s.value());
26                            }
27                        }
28                        _ => {}
29                    }
30                }
31            }
32        }
33    }
34
35    let mut columns = Vec::new();
36    let mut keys = Vec::new();
37    let mut relations = Vec::new();
38    let mut from_ms_fields = Vec::new();
39    let mut from_pg_fields = Vec::new();
40    let mut from_ms_fields_with_prefix = Vec::new();
41    let mut from_pg_fields_with_prefix = Vec::new();
42    let mut assoc_consts = Vec::new();
43    let mut insert_stmts = Vec::new();
44    let mut update_set_stmts = Vec::new();
45    let mut update_where_stmts = Vec::new();
46    let mut delete_where_stmts = Vec::new();
47    let mut validate_stmts = Vec::new();
48    let mut first_key_col = String::new();
49    let mut has_identity = false;
50    let mut key_trait_impls = Vec::new();
51
52    if let Data::Struct(ds) = input.data {
53        if let Fields::Named(fields_named) = ds.fields {
54            for field in fields_named.named {
55                let ident = field.ident.unwrap();
56                let ty = field.ty.clone();
57
58                let mut is_option = false;
59                let mut is_string = false;
60                let mut inner_ty = ty.clone();
61                if let syn::Type::Path(tp) = &ty {
62                    if tp.path.segments.len() == 1 && tp.path.segments[0].ident == "Option" {
63                        is_option = true;
64                        if let syn::PathArguments::AngleBracketed(args) = &tp.path.segments[0].arguments {
65                            if let Some(syn::GenericArgument::Type(t)) = args.args.first() {
66                                inner_ty = t.clone();
67                                if let syn::Type::Path(itp) = t {
68                                    if itp.path.is_ident("String") {
69                                        is_string = true;
70                                    }
71                                }
72                            }
73                        }
74                    } else if tp.path.is_ident("String") {
75                        is_string = true;
76                    }
77                }
78
79                // relation handling
80                let mut is_relation = false;
81                let mut rel_foreign_key = String::new();
82                let mut rel_table = String::new();
83                let mut rel_table_number: Option<u32> = None;
84                let mut rel_ignore_in_update = false;
85                let mut rel_ignore_in_insert = false;
86
87                for attr in field.attrs.iter() {
88                    if attr.path.is_ident("relation") {
89                        is_relation = true;
90                        if let Ok(Meta::List(list)) = attr.parse_meta() {
91                            for nested in list.nested.iter() {
92                                match nested {
93                                    NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("foreign_key") => {
94                                        if let Lit::Str(s) = &nv.lit {
95                                            rel_foreign_key = s.value();
96                                        }
97                                    }
98                                    NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("table") => {
99                                        if let Lit::Str(s) = &nv.lit {
100                                            rel_table = s.value();
101                                        }
102                                    }
103                                    NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("table_number") => {
104                                        if let Lit::Int(i) = &nv.lit {
105                                            rel_table_number = i.base10_parse().ok();
106                                        }
107                                    }
108                                    NestedMeta::Meta(Meta::Path(p)) if p.is_ident("ignore_in_update") => {
109                                        rel_ignore_in_update = true;
110                                    }
111                                    NestedMeta::Meta(Meta::Path(p)) if p.is_ident("ignore_in_insert") => {
112                                        rel_ignore_in_insert = true;
113                                    }
114                                    _ => {}
115                                }
116                            }
117                        }
118                    }
119                }
120
121                if is_relation {
122                    let table_num_tokens = match rel_table_number {
123                        Some(v) => quote! { Some(#v) },
124                        None => quote! { None },
125                    };
126                    relations.push(quote! {
127                        ::rquery_orm::mapping::RelationMeta {
128                            name: stringify!(#ident),
129                            foreign_key: #rel_foreign_key,
130                            table: #rel_table,
131                            table_number: #table_num_tokens,
132                            ignore_in_update: #rel_ignore_in_update,
133                            ignore_in_insert: #rel_ignore_in_insert,
134                        }
135                    });
136                    continue;
137                }
138
139                // column and key attributes
140                let mut col_name = ident.to_string();
141                let mut col_name_lit: Option<syn::LitStr> = None;
142                let mut is_key = false;
143                let mut is_identity = false;
144                let mut required = false;
145                let mut allow_null = false;
146                let mut max_length: Option<usize> = None;
147                let mut min_length: Option<usize> = None;
148                let mut allow_empty = true;
149                let mut regex: Option<String> = None;
150                let mut err_max_length: Option<String> = None;
151                let mut err_min_length: Option<String> = None;
152                let mut err_required: Option<String> = None;
153                let mut err_allow_null: Option<String> = None;
154                let mut err_allow_empty: Option<String> = None;
155                let mut err_regex: Option<String> = None;
156                let mut ignore = false;
157                let mut ignore_in_update = false;
158                let mut ignore_in_insert = false;
159                let mut ignore_in_delete = false;
160                let mut key_ignore_in_update = false;
161                let mut key_ignore_in_insert = false;
162
163                for attr in field.attrs.iter() {
164                    if attr.path.is_ident("column") {
165                        if let Ok(Meta::List(list)) = attr.parse_meta() {
166                            for nested in list.nested.iter() {
167                                match nested {
168                                    NestedMeta::Meta(Meta::NameValue(nv)) => {
169                                        if nv.path.is_ident("name") {
170                                            if let Lit::Str(s) = &nv.lit { col_name = s.value(); }
171                                        } else if nv.path.is_ident("max_length") {
172                                            if let Lit::Int(i) = &nv.lit { max_length = i.base10_parse().ok(); }
173                                        } else if nv.path.is_ident("min_length") {
174                                            if let Lit::Int(i) = &nv.lit { min_length = i.base10_parse().ok(); }
175                                        } else if nv.path.is_ident("regex") {
176                                            if let Lit::Str(s) = &nv.lit { regex = Some(s.value()); }
177                                        } else if nv.path.is_ident("error_max_length") {
178                                            if let Lit::Str(s) = &nv.lit { err_max_length = Some(s.value()); }
179                                        } else if nv.path.is_ident("error_min_length") {
180                                            if let Lit::Str(s) = &nv.lit { err_min_length = Some(s.value()); }
181                                        } else if nv.path.is_ident("error_required") {
182                                            if let Lit::Str(s) = &nv.lit { err_required = Some(s.value()); }
183                                        } else if nv.path.is_ident("error_allow_null") {
184                                            if let Lit::Str(s) = &nv.lit { err_allow_null = Some(s.value()); }
185                                        } else if nv.path.is_ident("error_allow_empty") {
186                                            if let Lit::Str(s) = &nv.lit { err_allow_empty = Some(s.value()); }
187                                        } else if nv.path.is_ident("error_regex") {
188                                            if let Lit::Str(s) = &nv.lit { err_regex = Some(s.value()); }
189                                        } else if nv.path.is_ident("allow_empty") {
190                                            if let Lit::Bool(b) = &nv.lit { allow_empty = b.value; }
191                                        } else if nv.path.is_ident("required") {
192                                            if let Lit::Bool(b) = &nv.lit { required = b.value; }
193                                        } else if nv.path.is_ident("allow_null") {
194                                            if let Lit::Bool(b) = &nv.lit { allow_null = b.value; }
195                                        } else if nv.path.is_ident("ignore_in_update") {
196                                            if let Lit::Bool(b) = &nv.lit { ignore_in_update = b.value; }
197                                        } else if nv.path.is_ident("ignore_in_insert") {
198                                            if let Lit::Bool(b) = &nv.lit { ignore_in_insert = b.value; }
199                                        } else if nv.path.is_ident("ignore_in_delete") {
200                                            if let Lit::Bool(b) = &nv.lit { ignore_in_delete = b.value; }
201                                        } else if nv.path.is_ident("ignore") {
202                                            if let Lit::Bool(b) = &nv.lit { ignore = b.value; }
203                                        }
204                                    }
205                                    NestedMeta::Meta(Meta::Path(p)) => {
206                                        if p.is_ident("required") { required = true; }
207                                        else if p.is_ident("allow_null") { allow_null = true; }
208                                        else if p.is_ident("allow_empty") { allow_empty = true; }
209                                        else if p.is_ident("ignore") { ignore = true; }
210                                        else if p.is_ident("ignore_in_update") { ignore_in_update = true; }
211                                        else if p.is_ident("ignore_in_insert") { ignore_in_insert = true; }
212                                        else if p.is_ident("ignore_in_delete") { ignore_in_delete = true; }
213                                    }
214                                    _ => {}
215                                }
216                            }
217                        }
218                    } else if attr.path.is_ident("key") {
219                        is_key = true;
220                        if let Ok(Meta::List(list)) = attr.parse_meta() {
221                            for nested in list.nested.iter() {
222                                match nested {
223                                    NestedMeta::Meta(Meta::NameValue(nv)) => {
224                                        if nv.path.is_ident("is_identity") {
225                                            if let Lit::Bool(b) = &nv.lit { is_identity = b.value; }
226                                        } else if nv.path.is_ident("name") {
227                                            if let Lit::Str(s) = &nv.lit { col_name = s.value(); }
228                                        } else if nv.path.is_ident("ignore_in_update") {
229                                            if let Lit::Bool(b) = &nv.lit { key_ignore_in_update = b.value; }
230                                        } else if nv.path.is_ident("ignore_in_insert") {
231                                            if let Lit::Bool(b) = &nv.lit { key_ignore_in_insert = b.value; }
232                                        }
233                                    }
234                                    NestedMeta::Meta(Meta::Path(p)) => {
235                                        if p.is_ident("ignore_in_update") { key_ignore_in_update = true; }
236                                        else if p.is_ident("ignore_in_insert") { key_ignore_in_insert = true; }
237                                    }
238                                    _ => {}
239                                }
240                            }
241                        }
242                    }
243                }
244
245                // literal for column name token
246                let col_name_lit_inner = syn::LitStr::new(&col_name, proc_macro2::Span::call_site());
247
248                let max_length_token = match max_length { Some(v) => quote! { Some(#v) }, None => quote! { None } };
249                let min_length_token = match min_length { Some(v) => quote! { Some(#v) }, None => quote! { None } };
250                let regex_token = match regex.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
251                let err_max_length_token = match err_max_length.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
252                let err_min_length_token = match err_min_length.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
253                let err_required_token = match err_required.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
254                let err_allow_null_token = match err_allow_null.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
255                let err_allow_empty_token = match err_allow_empty.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
256                let err_regex_token = match err_regex.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
257
258                columns.push(quote! {
259                    ::rquery_orm::mapping::ColumnMeta {
260                        name: #col_name_lit_inner,
261                        required: #required,
262                        allow_null: #allow_null,
263                        max_length: #max_length_token,
264                        min_length: #min_length_token,
265                        allow_empty: #allow_empty,
266                        regex: #regex_token,
267                        error_max_length: #err_max_length_token,
268                        error_min_length: #err_min_length_token,
269                        error_required: #err_required_token,
270                        error_allow_null: #err_allow_null_token,
271                        error_allow_empty: #err_allow_empty_token,
272                        error_regex: #err_regex_token,
273                        ignore: #ignore,
274                        ignore_in_update: #ignore_in_update,
275                        ignore_in_insert: #ignore_in_insert,
276                        ignore_in_delete: #ignore_in_delete,
277                    }
278                });
279
280                if is_key {
281                    keys.push(quote! {
282                        ::rquery_orm::mapping::KeyMeta {
283                            column: #col_name,
284                            is_identity: #is_identity,
285                            ignore_in_update: #key_ignore_in_update,
286                            ignore_in_insert: #key_ignore_in_insert,
287                        }
288                    });
289                    if first_key_col.is_empty() {
290                        first_key_col = col_name.clone();
291                        if let syn::Type::Path(tp) = &ty {
292                            if tp.qself.is_none() {
293                                if tp.path.is_ident("i32") {
294                                    key_trait_impls.push(quote! { impl ::rquery_orm::mapping::KeyAsInt for #struct_name { fn key(&self) -> i32 { self.#ident } } });
295                                } else if tp.path.is_ident("String") {
296                                    key_trait_impls.push(quote! { impl ::rquery_orm::mapping::KeyAsString for #struct_name { fn key(&self) -> String { self.#ident.clone() } } });
297                                } else {
298                                    let last = tp.path.segments.last().unwrap().ident.to_string();
299                                    if last == "Uuid" {
300                                        key_trait_impls.push(quote! { impl ::rquery_orm::mapping::KeyAsGuid for #struct_name { fn key(&self) -> uuid::Uuid { self.#ident } } });
301                                    }
302                                }
303                            }
304                        }
305                    }
306                    if is_identity { has_identity = true; }
307                }
308
309                // push associated const for this column
310                assoc_consts.push(quote! { pub const #ident: &'static str = #col_name_lit_inner; });
311
312                if is_option {
313                    if is_string {
314                        from_ms_fields.push(quote! { #ident: row.try_get::<&str, _>(#col_name_lit_inner)?.map(|v| v.to_string()) });
315                        from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<&str, _>(k.as_str())?.map(|v| v.to_string()) } });
316                    } else {
317                        from_ms_fields.push(quote! { #ident: row.try_get::<#inner_ty, _>(#col_name_lit_inner)? });
318                        from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<#inner_ty, _>(k.as_str())? } });
319                    }
320                    from_pg_fields.push(quote! { #ident: row.try_get(#col_name_lit_inner)? });
321                    from_pg_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get(k.as_str())? } });
322                } else {
323                    if is_string {
324                        from_ms_fields.push(quote! { #ident: row.try_get::<&str, _>(#col_name_lit_inner)?.unwrap().to_string() });
325                        from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<&str, _>(k.as_str())?.unwrap().to_string() } });
326                    } else {
327                        from_ms_fields.push(quote! { #ident: row.try_get::<#inner_ty, _>(#col_name_lit_inner)?.unwrap() });
328                        from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<#inner_ty, _>(k.as_str())?.unwrap() } });
329                    }
330                    from_pg_fields.push(quote! { #ident: row.try_get(#col_name_lit_inner)? });
331                    from_pg_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get(k.as_str())? } });
332                }
333
334                if !is_identity && !ignore && !ignore_in_insert && !key_ignore_in_insert {
335                    insert_stmts.push(quote! {
336                        cols.push(#col_name);
337                        vals.push(match style {
338                            ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
339                            ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
340                        });
341                        params.push(self.#ident.clone().to_param());
342                        idx += 1;
343                    });
344                }
345
346                if !is_key {
347                    if !ignore && !ignore_in_update {
348                        update_set_stmts.push(quote! {
349                            sets.push(format!("{} = {}", #col_name, match style {
350                                ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
351                                ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
352                            }));
353                            params.push(self.#ident.clone().to_param());
354                            idx += 1;
355                        });
356                    }
357                } else if !key_ignore_in_update {
358                    update_where_stmts.push(quote! {
359                        wheres.push(format!("{} = {}", #col_name, match style {
360                            ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
361                            ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
362                        }));
363                        params.push(self.#ident.clone().to_param());
364                        idx += 1;
365                    });
366                    delete_where_stmts.push(quote! {
367                        wheres.push(format!("{} = {}", #col_name, match style {
368                            ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
369                            ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
370                        }));
371                        params.push(self.#ident.clone().to_param());
372                        idx += 1;
373                    });
374                }
375
376                // validation generation
377                let field_literal = col_name.clone();
378                let required_push = if let Some(msg) = err_required.clone() {
379                    quote! { errors.push(#msg.to_string()); }
380                } else {
381                    let n = field_literal.clone();
382                    quote! { errors.push(format!("{} is required", #n)); }
383                };
384                let allow_null_push = if let Some(msg) = err_allow_null.clone() {
385                    quote! { errors.push(#msg.to_string()); }
386                } else {
387                    let n = field_literal.clone();
388                    quote! { errors.push(format!("{} cannot be null", #n)); }
389                };
390                let allow_empty_push = if let Some(msg) = err_allow_empty.clone() {
391                    quote! { errors.push(#msg.to_string()); }
392                } else {
393                    let n = field_literal.clone();
394                    quote! { errors.push(format!("{} cannot be empty", #n)); }
395                };
396                let max_check = if let Some(max) = max_length {
397                    let err_push = if let Some(msg) = err_max_length.clone() {
398                        quote! { errors.push(#msg.to_string()); }
399                    } else {
400                        let n = field_literal.clone();
401                        quote! { errors.push(format!("{} exceeds max length {}", #n, #max)); }
402                    };
403                    quote! { if value.len() > #max { #err_push } }
404                } else { quote! {} };
405                let min_check = if let Some(min) = min_length {
406                    let err_push = if let Some(msg) = err_min_length.clone() {
407                        quote! { errors.push(#msg.to_string()); }
408                    } else {
409                        let n = field_literal.clone();
410                        quote! { errors.push(format!("{} below min length {}", #n, #min)); }
411                    };
412                    quote! { if value.len() < #min { #err_push } }
413                } else { quote! {} };
414                let regex_check = if let Some(re) = regex.clone() {
415                    let err_push = if let Some(msg) = err_regex.clone() {
416                        quote! { errors.push(#msg.to_string()); }
417                    } else {
418                        let n = field_literal.clone();
419                        quote! { errors.push(format!("{} has invalid format", #n)); }
420                    };
421                    quote! {{
422                        static RE: ::std::sync::OnceLock<::regex::Regex> = ::std::sync::OnceLock::new();
423                        let re = RE.get_or_init(|| ::regex::Regex::new(#re).unwrap());
424                        if !re.is_match(value) { #err_push }
425                    }}
426                } else { quote! {} };
427                if is_option {
428                    let some_branch = if is_string {
429                        quote! {
430                            if value.is_empty() {
431                                if #required {
432                                    #required_push
433                                } else if !#allow_empty {
434                                    #allow_empty_push
435                                }
436                            }
437                            #max_check
438                            #min_check
439                            #regex_check
440                        }
441                    } else {
442                        quote! {}
443                    };
444                    validate_stmts.push(quote! {
445                        match &self.#ident {
446                            None => {
447                                if #required {
448                                    #required_push
449                                } else if !#allow_null {
450                                    #allow_null_push
451                                }
452                            }
453                            Some(value) => { #some_branch }
454                        }
455                    });
456                } else if is_string {
457                    validate_stmts.push(quote! {
458                        let value = &self.#ident;
459                        if value.is_empty() {
460                            if #required {
461                                #required_push
462                            } else if !#allow_empty {
463                                #allow_empty_push
464                            }
465                        }
466                        #max_check
467                        #min_check
468                        #regex_check
469                    });
470                }
471            }
472        }
473    }
474
475    let table_name_lit = syn::LitStr::new(&table_name, proc_macro2::Span::call_site());
476    let schema_tokens = match table_schema {
477        Some(s) => {
478            let s_lit = syn::LitStr::new(&s, proc_macro2::Span::call_site());
479            quote! { Some(#s_lit) }
480        }
481        None => quote! { None },
482    };
483    let first_key_col_literal = first_key_col.clone();
484
485    let expanded = quote! {
486        const COLUMNS: &[::rquery_orm::mapping::ColumnMeta] = &[#(#columns),*];
487        const KEYS: &[::rquery_orm::mapping::KeyMeta] = &[#(#keys),*];
488        const RELATIONS: &[::rquery_orm::mapping::RelationMeta] = &[#(#relations),*];
489        static TABLE_META: ::rquery_orm::mapping::TableMeta = ::rquery_orm::mapping::TableMeta {
490            name: #table_name_lit,
491            schema: #schema_tokens,
492            columns: COLUMNS,
493            keys: KEYS,
494            relations: RELATIONS,
495        };
496
497        impl ::rquery_orm::mapping::Entity for #struct_name {
498            fn table() -> &'static ::rquery_orm::mapping::TableMeta {
499                &TABLE_META
500            }
501        }
502
503        impl ::rquery_orm::mapping::FromRowNamed for #struct_name {
504            fn from_row_ms(row: &tiberius::Row) -> anyhow::Result<Self> {
505                Ok(Self { #(#from_ms_fields),* })
506            }
507            fn from_row_pg(row: &tokio_postgres::Row) -> anyhow::Result<Self> {
508                Ok(Self { #(#from_pg_fields),* })
509            }
510        }
511
512        impl ::rquery_orm::mapping::FromRowWithPrefix for #struct_name {
513            fn from_row_ms_with(row: &tiberius::Row, prefix: &str) -> anyhow::Result<Self> {
514                Ok(Self { #(#from_ms_fields_with_prefix),* })
515            }
516            fn from_row_pg_with(row: &tokio_postgres::Row, prefix: &str) -> anyhow::Result<Self> {
517                Ok(Self { #(#from_pg_fields_with_prefix),* })
518            }
519        }
520
521        impl ::rquery_orm::mapping::Validatable for #struct_name {
522            fn validate(&self) -> Result<(), Vec<String>> {
523                let mut errors = Vec::new();
524                #(#validate_stmts)*
525                if errors.is_empty() { Ok(()) } else { Err(errors) }
526            }
527        }
528
529        impl ::rquery_orm::mapping::Persistable for #struct_name {
530            fn build_insert(&self, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>, bool) {
531                use ::rquery_orm::query::ToParam;
532                let mut cols = Vec::new();
533                let mut vals = Vec::new();
534                let mut params = Vec::new();
535                let mut idx = 1;
536                #(#insert_stmts)*
537                let sql = format!("INSERT INTO {} ({}) VALUES ({})", #table_name, cols.join(", "), vals.join(", "));
538                (sql, params, #has_identity)
539            }
540
541            fn build_update(&self, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>) {
542                use ::rquery_orm::query::ToParam;
543                let mut sets = Vec::new();
544                let mut wheres = Vec::new();
545                let mut params = Vec::new();
546                let mut idx = 1;
547                #(#update_set_stmts)*
548                #(#update_where_stmts)*
549                let sql = format!("UPDATE {} SET {} WHERE {}", #table_name, sets.join(", "), wheres.join(" AND "));
550                (sql, params)
551            }
552
553            fn build_delete(&self, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>) {
554                use ::rquery_orm::query::ToParam;
555                let mut wheres = Vec::new();
556                let mut params = Vec::new();
557                let mut idx = 1;
558                #(#delete_where_stmts)*
559                let sql = format!("DELETE FROM {} WHERE {}", #table_name, wheres.join(" AND "));
560                (sql, params)
561            }
562
563            fn build_delete_by_key(key: ::rquery_orm::query::SqlParam, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>) {
564                let placeholder = match style {
565                    ::rquery_orm::query::PlaceholderStyle::AtP => "@P1".to_string(),
566                    ::rquery_orm::query::PlaceholderStyle::Dollar => "$1".to_string(),
567                };
568                let sql = format!("DELETE FROM {} WHERE {} = {}", #table_name, #first_key_col_literal, placeholder);
569                (sql, vec![key])
570            }
571        }
572
573        impl #struct_name {
574            pub const TABLE: &'static str = #table_name_lit;
575            #(#assoc_consts)*
576        }
577
578        #(#key_trait_impls)*
579    };
580
581    TokenStream::from(expanded)
582}