sqlx_models_derive/
lib.rs

1extern crate proc_macro;
2use convert_case::{Case, Casing};
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::__private::TokenStream2;
6use syn::parse::{Parse, ParseStream, Result};
7use syn::{
8    braced, parenthesized, parse_macro_input, parse_str, punctuated::Punctuated, token::Comma,
9    Attribute, BareFnArg, Field, Fields, Ident, ItemStruct, LitStr, Path, PathArguments, Token,
10    Type, TypePath,
11};
12
13mod kw {
14    syn::custom_keyword!(table);
15    syn::custom_keyword!(state);
16    syn::custom_keyword!(queries);
17    syn::custom_keyword!(has_many);
18    syn::custom_keyword!(belongs_to);
19    syn::custom_keyword!(default);
20    syn::custom_keyword!(no_update);
21    syn::custom_keyword!(no_insert);
22    syn::custom_keyword!(no_delete);
23}
24
25#[derive(Debug)]
26struct Query {
27    method_name: Ident,
28    sql: LitStr,
29    args: Punctuated<BareFnArg, Comma>,
30}
31
32impl Parse for Query {
33    fn parse(input: ParseStream) -> Result<Self> {
34        let method_name: Ident = input.parse()?;
35        let content;
36        parenthesized!(content in input);
37        let sql: LitStr = content.parse()?;
38        let args: Punctuated<BareFnArg, Comma> = match content.parse::<Token![,]>() {
39            Ok(_) => content.parse_terminated(BareFnArg::parse)?,
40            _ => Punctuated::new(),
41        };
42        Ok(Query {
43            method_name,
44            sql,
45            args,
46        })
47    }
48}
49
50#[derive(Debug)]
51struct Association {
52    model_name: Ident,
53    column_name: Ident,
54}
55
56impl Parse for Association {
57    fn parse(input: ParseStream) -> Result<Self> {
58        let model_name: Ident = input.parse()?;
59        let content;
60        parenthesized!(content in input);
61        let column_name: Ident = content.parse()?;
62        Ok(Association {
63            model_name,
64            column_name,
65        })
66    }
67}
68
69#[derive(Debug)]
70enum ModelConfig {
71    Queries(Punctuated<Query, Comma>),
72    HasMany(Punctuated<Association, Comma>),
73    BelongsTo(Punctuated<Association, Comma>),
74}
75
76impl Parse for ModelConfig {
77    fn parse(input: ParseStream) -> Result<Self> {
78        if input.peek(kw::queries) {
79            let _ = input.parse::<kw::queries>()?;
80            let content;
81            braced!(content in input);
82            let queries = content.parse_terminated(Query::parse)?;
83            Ok(ModelConfig::Queries(queries))
84        } else if input.peek(kw::has_many) {
85            let _ = input.parse::<kw::has_many>()?;
86            let content;
87            braced!(content in input);
88            let associations = content.parse_terminated(Association::parse)?;
89            Ok(ModelConfig::HasMany(associations))
90        } else if input.peek(kw::belongs_to) {
91            let _ = input.parse::<kw::belongs_to>()?;
92            let content;
93            braced!(content in input);
94            let associations = content.parse_terminated(Association::parse)?;
95            Ok(ModelConfig::BelongsTo(associations))
96        } else {
97            panic!("Unexpected model config name");
98        }
99    }
100}
101
102#[derive(Debug)]
103struct ModelHints {
104    ty: Ident,
105    default: bool,
106    op_ne: bool,
107    op_gt: bool,
108    op_gte: bool,
109    op_lt: bool,
110    op_lte: bool,
111    op_like: bool,
112    op_not_like: bool,
113    op_ilike: bool,
114    op_not_ilike: bool,
115    op_similar_to: bool,
116    op_not_similar_to: bool,
117    op_in: bool,
118    op_not_in: bool,
119    op_is_set: bool,
120}
121
122impl Parse for ModelHints {
123    fn parse(input: ParseStream) -> Result<Self> {
124        let ty: Ident = input.parse()?;
125
126        let mut hints = ModelHints {
127            ty,
128            default: false,
129            op_ne: false,
130            op_gt: false,
131            op_gte: false,
132            op_lt: false,
133            op_lte: false,
134            op_like: false,
135            op_not_like: false,
136            op_ilike: false,
137            op_not_ilike: false,
138            op_similar_to: false,
139            op_not_similar_to: false,
140            op_in: false,
141            op_not_in: false,
142            op_is_set: false,
143        };
144
145        while !input.is_empty() {
146            // Consume a comma if there are more tokens
147            if input.peek(Token![,]) {
148                input.parse::<Token![,]>()?;
149            } else {
150                break;
151            }
152
153            match input.parse::<Ident>()?.to_string().as_str() {
154                "default" => hints.default = true,
155                "all_ops" => {
156                    hints.op_ne = true;
157                    hints.op_gt = true;
158                    hints.op_gte = true;
159                    hints.op_lt = true;
160                    hints.op_lte = true;
161                    hints.op_like = true;
162                    hints.op_not_like = true;
163                    hints.op_ilike = true;
164                    hints.op_not_ilike = true;
165                    hints.op_similar_to = true;
166                    hints.op_not_similar_to = true;
167                    hints.op_in = true;
168                    hints.op_not_in = true;
169                    hints.op_is_set = true;
170                }
171                "op_ne" => hints.op_ne = true,
172                "op_gt" => hints.op_gt = true,
173                "op_gte" => hints.op_gte = true,
174                "op_lt" => hints.op_lt = true,
175                "op_lte" => hints.op_lte = true,
176                "op_like" => hints.op_like = true,
177                "op_not_like" => hints.op_not_like = true,
178                "op_ilike" => hints.op_ilike = true,
179                "op_not_ilike" => hints.op_not_ilike = true,
180                "op_similar_to" => hints.op_similar_to = true,
181                "op_not_similar_to" => hints.op_not_similar_to = true,
182                "op_in" => hints.op_in = true,
183                "op_not_in" => hints.op_not_in = true,
184                "op_is_set" => hints.op_is_set = true,
185                other => {
186                    panic!("Unknown flag for field {other}")
187                }
188            }
189        }
190
191        Ok(hints)
192    }
193}
194
195#[derive(Debug)]
196struct SqlxModelConf {
197    id_type: Type,
198    struct_name: Ident,
199    extra_struct_attributes: Vec<Attribute>,
200    attrs_struct: Ident,
201    state_name: Ident,
202    table_name: Ident,
203    fields: Punctuated<Field, Comma>,
204    queries: Punctuated<Query, Comma>,
205    has_many: Punctuated<Association, Comma>,
206    belongs_to: Punctuated<Association, Comma>,
207    hub_struct: Ident,
208    sql_select_columns: String,
209    field_idents: Vec<Ident>,
210    hub_builder_method: Ident,
211    no_update: bool,
212    no_insert: bool,
213    no_delete: bool,
214}
215
216impl Parse for SqlxModelConf {
217    fn parse(input: ParseStream) -> Result<Self> {
218        let _ = input.parse::<kw::state>()?;
219        input.parse::<Token![:]>()?;
220        let state_name: Ident = input.parse()?;
221        input.parse::<Token![,]>()?;
222        let _ = input.parse::<kw::table>()?;
223        input.parse::<Token![:]>()?;
224        let table_name: Ident = input.parse()?;
225        input.parse::<Token![,]>()?;
226
227        let no_update = if input.peek(kw::no_update) {
228            input.parse::<kw::no_update>()?;
229            input.parse::<Token![,]>()?;
230            true
231        } else {
232            false
233        };
234
235        let no_insert = if input.peek(kw::no_insert) {
236            input.parse::<kw::no_insert>()?;
237            input.parse::<Token![,]>()?;
238            true
239        } else {
240            false
241        };
242
243        let no_delete = if input.peek(kw::no_delete) {
244            input.parse::<kw::no_delete>()?;
245            input.parse::<Token![,]>()?;
246            true
247        } else {
248            false
249        };
250
251        let whole_struct: ItemStruct = input.parse()?;
252
253        let struct_name: Ident = whole_struct.ident.clone();
254        let named_fields = match whole_struct.fields.clone() {
255            Fields::Named(x) => x,
256            _ => panic!("Struct needs named fields"),
257        };
258
259        let mut queries: Punctuated<Query, Comma> = Punctuated::new();
260        let mut has_many: Punctuated<Association, Comma> = Punctuated::new();
261        let mut belongs_to: Punctuated<Association, Comma> = Punctuated::new();
262
263        if input.parse::<Token![,]>().is_ok() {
264            let configs: Punctuated<ModelConfig, Comma> =
265                input.parse_terminated(ModelConfig::parse)?;
266            for config in configs {
267                match config {
268                    ModelConfig::Queries(a) => queries = a,
269                    ModelConfig::HasMany(a) => has_many = a,
270                    ModelConfig::BelongsTo(a) => belongs_to = a,
271                }
272            }
273        }
274
275        let extra_struct_attributes = whole_struct.attrs.clone();
276
277        let attrs_struct = format_ident!("{}Attrs", &struct_name);
278
279        let fields = named_fields.named;
280
281        let hub_struct = format_ident!("{}Hub", struct_name);
282        let sql_select_columns = fields
283            .iter()
284            .map(|f| {
285                let name = f.ident.as_ref().unwrap();
286                let ty = &f.ty;
287                format!(r#"{} as "{}!: {}""#, name, name, quote! { #ty })
288            })
289            .collect::<Vec<String>>()
290            .join(", \n");
291
292        let field_idents: Vec<Ident> = fields
293            .clone()
294            .into_iter()
295            .map(|i| i.ident.unwrap())
296            .collect();
297
298        let id_type = fields
299            .iter()
300            .find(|i| i.ident.as_ref().unwrap() == "id")
301            .expect("struct to have an id field")
302            .ty
303            .clone();
304
305        let hub_builder_method = Ident::new(
306            &struct_name.to_string().to_case(Case::Snake),
307            struct_name.span(),
308        );
309
310        Ok(SqlxModelConf {
311            id_type,
312            extra_struct_attributes,
313            state_name,
314            struct_name,
315            attrs_struct,
316            table_name,
317            fields,
318            queries,
319            has_many,
320            belongs_to,
321            hub_struct,
322            sql_select_columns,
323            field_idents,
324            hub_builder_method,
325            no_update,
326            no_insert,
327            no_delete,
328        })
329    }
330}
331
332#[proc_macro]
333pub fn model(tokens: TokenStream) -> TokenStream {
334    let conf = parse_macro_input!(tokens as SqlxModelConf);
335    let state_name = &conf.state_name;
336    let hub_struct = &conf.hub_struct;
337    let hub_builder_method = &conf.hub_builder_method;
338
339    let base_section = build_base(&conf);
340    let select_section = build_select(&conf);
341    let insert_section = if conf.no_insert {
342        quote! {}
343    } else {
344        build_insert(&conf)
345    };
346    let update_section = if conf.no_update {
347        quote! {}
348    } else {
349        build_update(&conf)
350    };
351    let delete_section = if conf.no_delete {
352        quote! {}
353    } else {
354        build_delete(&conf)
355    };
356    let queries_section = build_queries(&conf);
357
358    let quoted = quote! {
359      pub struct #hub_struct {
360        state: #state_name,
361      }
362
363      impl #state_name {
364        pub fn #hub_builder_method(&self) -> #hub_struct {
365          #hub_struct::new(self.clone())
366        }
367      }
368
369      impl #hub_struct {
370        pub fn new(state: #state_name) -> Self {
371          Self{ state }
372        }
373
374        pub async fn transactional(mut self) -> sqlx::Result<Self> {
375          self.state.db = self.state.db.transaction().await?;
376          Ok(self)
377        }
378
379        pub async fn commit(&self) -> sqlx::Result<()> {
380          self.state.db.commit().await?;
381          Ok(())
382        }
383      }
384
385      #base_section
386
387      #select_section
388
389      #insert_section
390
391      #update_section
392
393      #delete_section
394
395      #(#queries_section)*
396    };
397
398    quoted.into()
399}
400
401fn build_base(conf: &SqlxModelConf) -> TokenStream2 {
402    let state_name = &conf.state_name;
403    let struct_name = &conf.struct_name;
404    let hub_struct = &conf.hub_struct;
405    let attrs_struct = &conf.attrs_struct;
406    let field_idents = &conf.field_idents;
407    let id_type = &conf.id_type;
408    let extra_struct_attributes = &conf.extra_struct_attributes;
409    let hub_builder_method = &conf.hub_builder_method;
410    let select_struct = format_ident!("Select{}Hub", &struct_name);
411    let select_attrs_struct = format_ident!("Select{}", &struct_name);
412    let model_order_by = format_ident!("{}OrderBy", &struct_name);
413    let struct_name_as_string = LitStr::new(&struct_name.to_string(), struct_name.span());
414    let field_types: Vec<Type> = conf.fields.clone().into_iter().map(|i| i.ty).collect();
415
416    let mut belongs_to_structs: Vec<Ident> = vec![];
417    let mut belongs_to_builders: Vec<Ident> = vec![];
418    let mut belongs_to_columns: Vec<Ident> = vec![];
419    let mut maybe_belongs_to_structs: Vec<Ident> = vec![];
420    let mut maybe_belongs_to_builders: Vec<Ident> = vec![];
421    let mut maybe_belongs_to_columns: Vec<Ident> = vec![];
422
423    for c in &conf.belongs_to {
424        let field = conf
425            .fields
426            .iter()
427            .find(|x| *x.ident.as_ref().unwrap() == c.column_name)
428            .unwrap_or_else(|| {
429                panic!(
430                    "Belongs to column {:?} is not a field",
431                    c.column_name.to_string()
432                )
433            });
434
435        let is_option = if let Type::Path(TypePath {
436            path: Path { segments, .. },
437            ..
438        }) = &field.ty
439        {
440            segments[0].ident == "Option"
441        } else {
442            false
443        };
444
445        let builder = Ident::new(
446            &c.model_name.to_string().to_case(Case::Snake),
447            struct_name.span(),
448        );
449
450        if is_option {
451            maybe_belongs_to_structs.push(c.model_name.clone());
452            maybe_belongs_to_columns.push(c.column_name.clone());
453            maybe_belongs_to_builders.push(builder);
454        } else {
455            belongs_to_structs.push(c.model_name.clone());
456            belongs_to_columns.push(c.column_name.clone());
457            belongs_to_builders.push(builder);
458        }
459    }
460
461    let mut has_many_structs: Vec<Ident> = vec![];
462    let mut has_many_builders: Vec<Ident> = vec![];
463    let mut has_many_methods: Vec<Ident> = vec![];
464    let mut has_many_scope_methods: Vec<Ident> = vec![];
465    let mut has_many_select_structs: Vec<Ident> = vec![];
466    let mut has_many_columns: Vec<Ident> = vec![];
467
468    for c in &conf.has_many {
469        let builder = Ident::new(
470            &c.model_name.to_string().to_case(Case::Snake),
471            struct_name.span(),
472        );
473        has_many_methods.push(format_ident!("{}_vec", builder));
474        has_many_scope_methods.push(format_ident!("{}_scope", builder));
475        has_many_select_structs.push(format_ident!("Select{}Hub", c.model_name));
476        has_many_structs.push(c.model_name.clone());
477        has_many_columns.push(format_ident!("{}_eq", c.column_name));
478        has_many_builders.push(builder.clone());
479    }
480
481    let field_attrs: Vec<Vec<Attribute>> = conf
482        .fields
483        .clone()
484        .into_iter()
485        .map(|field| {
486            field
487                .attrs
488                .into_iter()
489                .filter(|a| a.path != parse_str("sqlx_model_hints").unwrap())
490                .collect::<Vec<Attribute>>()
491        })
492        .collect();
493
494    quote! {
495      impl #hub_struct {
496        fn init(&self, attrs: #attrs_struct) -> #struct_name {
497          #struct_name::new(self.state.clone(), attrs)
498        }
499      }
500
501      #[derive(Clone, serde::Serialize)]
502      pub struct #struct_name {
503        #[serde(skip_serializing)]
504        pub state: #state_name,
505        #[serde(flatten)]
506        pub attrs: #attrs_struct,
507      }
508
509      impl #struct_name {
510        pub fn new(state: #state_name, attrs: #attrs_struct) -> Self {
511          Self{ state, attrs }
512        }
513
514        pub async fn reload(&mut self) -> sqlx::Result<()> {
515          self.attrs = self.reloaded().await?.attrs;
516          Ok(())
517        }
518
519        pub async fn reloaded(&self) -> sqlx::Result<Self> {
520          self.state.#hub_builder_method().find(self.id()).await
521        }
522
523        #(
524          pub fn #field_idents<'a>(&'a self) -> &'a #field_types {
525            &self.attrs.#field_idents
526          }
527        )*
528
529        #(
530          pub async fn #belongs_to_builders(&self) -> sqlx::Result<#belongs_to_structs> {
531            self.state.#belongs_to_builders().find(self.#belongs_to_columns()).await
532          }
533        )*
534
535        #(
536          pub async fn #maybe_belongs_to_builders(&self) -> sqlx::Result<Option<#maybe_belongs_to_structs>> {
537            if let Some(a) = self.#maybe_belongs_to_columns() {
538              self.state.#maybe_belongs_to_builders().find(a).await.map(Some)
539            } else {
540              Ok(None)
541            }
542          }
543        )*
544
545        #(
546          pub fn #has_many_scope_methods(&self) -> #has_many_select_structs {
547            self.state.#has_many_builders().select().#has_many_columns(self.id())
548          }
549        )*
550
551        #(
552          pub async fn #has_many_methods(&self) -> sqlx::Result<Vec<#has_many_structs>> {
553            self.#has_many_scope_methods().all().await
554          }
555        )*
556      }
557
558      #[sqlx_models_orm::async_trait]
559      impl sqlx_models_orm::SqlxModel for #struct_name {
560        type State = #state_name;
561        type SelectModelHub = #select_struct;
562        type SelectModel = #select_attrs_struct;
563        type ModelOrderBy = #model_order_by;
564        type ModelHub = #hub_struct;
565        type Id = #id_type;
566      }
567
568      impl PartialEq for #struct_name {
569        fn eq(&self, other: &Self) -> bool {
570          self.attrs == other.attrs
571        }
572      }
573
574      #(#extra_struct_attributes)*
575      #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
576      pub struct #attrs_struct {
577        #(
578          #(#field_attrs)*
579          pub #field_idents: #field_types,
580        )*
581      }
582
583      impl std::fmt::Debug for #struct_name {
584        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
585          f.debug_struct(#struct_name_as_string)
586           .field("attrs", &self.attrs)
587           .finish()
588        }
589      }
590    }
591}
592
593fn build_select(conf: &SqlxModelConf) -> TokenStream2 {
594    let state_name = &conf.state_name;
595    let struct_name = &conf.struct_name;
596    let hub_struct = &conf.hub_struct;
597    let table_name = &conf.table_name;
598    let attrs_struct = &conf.attrs_struct;
599    let field_idents = &conf.field_idents;
600    let select_struct = format_ident!("Select{}Hub", &struct_name);
601    let model_order_by = format_ident!("{}OrderBy", &struct_name);
602    let select_attrs_struct = format_ident!("Select{}", &struct_name);
603    let id_type = &conf.id_type;
604    let span = conf.struct_name.span();
605
606    let mut comparison_idents: Vec<Ident> = vec![];
607    let mut comparison_types: Vec<Type> = vec![];
608    let mut builder_method_simple_idents: Vec<Ident> = vec![];
609    let mut builder_method_simple_types: Vec<Type> = vec![];
610    let mut builder_method_string_idents: Vec<Ident> = vec![];
611    let mut where_clauses = vec![];
612    let mut args = vec![];
613
614    let sort_variants: Vec<Ident> = field_idents
615        .iter()
616        .map(|i| Ident::new(&i.to_string().to_case(Case::UpperCamel), i.span()))
617        .collect();
618
619    for field in conf.fields.clone().into_iter() {
620        let ty = field.ty.clone();
621        let flat_ty: syn::Type = if let Type::Path(TypePath {
622            path: Path { segments, .. },
623            ..
624        }) = &ty
625        {
626            if &segments[0].ident.to_string() == "Option" {
627                match &segments[0].arguments {
628                    PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
629                        args,
630                        ..
631                    }) => {
632                        let found = &args[0];
633                        syn::parse_quote! { #found }
634                    }
635                    _ => panic!(
636                        "Type {:?} is too complex. Only simple Option<type> are supported.",
637                        &ty
638                    ),
639                }
640            } else {
641                field.ty.clone()
642            }
643        } else {
644            panic!("Type {:?} expected to be type or Option<type>", &ty);
645        };
646
647        let ident = &field.ident.as_ref().unwrap();
648
649        if let Some(found) = field
650            .attrs
651            .iter()
652            .find(|a| a.path == parse_str("sqlx_model_hints").unwrap())
653        {
654            let hints = found
655                .parse_args::<ModelHints>()
656                .unwrap_or_else(|_| panic!("Arguments for sqlx_model_hints {:?}", found));
657            let db_type = hints.ty.to_string();
658            let mut field_position = args.len();
659
660            let mut comparisons = vec![(format_ident!("{}_eq", ident), "=", &flat_ty, true)];
661
662            if hints.op_ne {
663                comparisons.push((format_ident!("{}_ne", ident), "!=", &flat_ty, true));
664            }
665            if hints.op_gt {
666                comparisons.push((format_ident!("{}_gt", ident), ">", &flat_ty, true));
667            }
668            if hints.op_gte {
669                comparisons.push((format_ident!("{}_gte", ident), ">=", &flat_ty, true));
670            }
671            if hints.op_lt {
672                comparisons.push((format_ident!("{}_lt", ident), "<", &flat_ty, true));
673            }
674            if hints.op_lte {
675                comparisons.push((format_ident!("{}_lte", ident), "<=", &flat_ty, true));
676            }
677
678            let string_ty: syn::Type = syn::parse_quote! { String };
679            if &db_type == "varchar" || &db_type == "text" {
680                if hints.op_like {
681                    comparisons.push((format_ident!("{}_like", ident), "LIKE", &string_ty, false));
682                }
683                if hints.op_not_like {
684                    comparisons.push((
685                        format_ident!("{}_not_like", ident),
686                        "NOT LIKE",
687                        &string_ty,
688                        false,
689                    ));
690                }
691                if hints.op_ilike {
692                    comparisons.push((
693                        format_ident!("{}_ilike", ident),
694                        "ILIKE",
695                        &string_ty,
696                        false,
697                    ));
698                }
699                if hints.op_not_ilike {
700                    comparisons.push((
701                        format_ident!("{}_not_ilike", ident),
702                        "NOT ILIKE",
703                        &string_ty,
704                        false,
705                    ));
706                }
707                if hints.op_similar_to {
708                    comparisons.push((
709                        format_ident!("{}_similar_to", ident),
710                        "SIMILAR TO",
711                        &string_ty,
712                        false,
713                    ));
714                }
715                if hints.op_not_similar_to {
716                    comparisons.push((
717                        format_ident!("{}_not_similar_to", ident),
718                        "NOT SIMILAR TO",
719                        &string_ty,
720                        false,
721                    ));
722                }
723            }
724
725            for (comparison_ident, operator, rust_type, simple_builder) in comparisons.into_iter() {
726                comparison_idents.push(comparison_ident.clone());
727                comparison_types.push(rust_type.clone());
728                where_clauses.push(format!(
729                    "(NOT ${}::boolean OR {} {} ${}::{})",
730                    field_position + 1,
731                    &ident,
732                    operator,
733                    field_position + 2,
734                    &db_type
735                ));
736                field_position += 2;
737
738                args.push(quote! { self.#comparison_ident.is_some() });
739                args.push(quote! { &self.#comparison_ident as &Option<#rust_type> });
740
741                if simple_builder {
742                    builder_method_simple_idents.push(comparison_ident.clone());
743                    builder_method_simple_types.push(rust_type.clone());
744                } else {
745                    builder_method_string_idents.push(comparison_ident.clone());
746                }
747            }
748
749            let vec_of_ty: syn::Type = syn::parse_quote! { Vec<#flat_ty> };
750            let mut field_in_comparisons = vec![];
751            if hints.op_in {
752                field_in_comparisons.push((format_ident!("{}_in", ident), "= ANY", &vec_of_ty));
753            }
754            if hints.op_not_in {
755                field_in_comparisons.push((
756                    format_ident!("{}_not_in", ident),
757                    "<> ALL",
758                    &vec_of_ty,
759                ));
760            }
761
762            for (comparison_ident, operator, rust_type) in field_in_comparisons.into_iter() {
763                comparison_idents.push(comparison_ident.clone());
764                comparison_types.push(rust_type.clone());
765                where_clauses.push(format!(
766                    "(NOT ${}::boolean OR {} {}(CAST(${} as {}[])) )",
767                    field_position + 1,
768                    &ident,
769                    operator,
770                    field_position + 2,
771                    &db_type
772                ));
773                field_position += 2;
774
775                args.push(quote! { self.#comparison_ident.is_some() });
776                args.push(quote! { &self.#comparison_ident as &Option<#rust_type> });
777
778                builder_method_simple_idents.push(comparison_ident.clone());
779                builder_method_simple_types.push(rust_type.clone());
780            }
781
782            if hints.op_is_set {
783                field_position += 1;
784                let is_set_field_ident = format_ident!("{}_is_set", ident);
785                let bool_type: syn::Type = syn::parse_quote! { bool };
786                comparison_idents.push(is_set_field_ident.clone());
787                comparison_types.push(bool_type.clone());
788                where_clauses.push(
789                    format!(
790                      "(${}::boolean IS NULL OR ((${}::boolean AND {} IS NOT NULL) OR (NOT ${}::boolean AND {} IS NULL)))",
791                      field_position,
792                      field_position,
793                      &ident,
794                      field_position,
795                      &ident,
796                    )
797                );
798                args.push(quote! { self.#is_set_field_ident });
799                builder_method_simple_idents.push(is_set_field_ident.clone());
800                builder_method_simple_types.push(bool_type);
801            }
802        };
803    }
804
805    let sort_field_pos = args.len() + 1;
806    let desc_field_pos = args.len() + 2;
807    let limit_field_pos = args.len() + 3;
808    let offset_field_pos = args.len() + 4;
809    args.push(quote! { self.order_by.map(|i| format!("{:?}", i)) as Option<String> });
810    args.push(quote! { self.desc as bool });
811    args.push(quote! { self.limit as Option<i64> });
812    args.push(quote! { self.offset as Option<i64> });
813
814    let mut args_for_count = args.clone();
815    args_for_count.truncate(args.len() - 4);
816
817    let select_struct_str = LitStr::new(&select_struct.to_string(), span);
818
819    let comparison_idents_as_str: Vec<LitStr> = comparison_idents
820        .iter()
821        .map(|i| LitStr::new(&i.to_string(), span))
822        .collect();
823
824    let query_for_find_sort_criteria: String = field_idents
825        .iter()
826        .map(|f| {
827            let variant_name = f.to_string().to_case(Case::UpperCamel);
828            format!(
829                r#"
830      (CASE (${} = '{}' AND NOT ${}) WHEN true THEN {} ELSE NULL END),
831      (CASE (${} = '{}' AND ${}) WHEN true THEN {} ELSE NULL END) DESC
832    "#,
833                sort_field_pos,
834                variant_name,
835                desc_field_pos,
836                f,
837                sort_field_pos,
838                variant_name,
839                desc_field_pos,
840                f
841            )
842        })
843        .collect::<Vec<String>>()
844        .join(",");
845
846    let query_for_find = LitStr::new(
847        &format!(
848            "SELECT {} FROM {} WHERE {} ORDER BY {} LIMIT ${} OFFSET ${}",
849            &conf.sql_select_columns,
850            table_name,
851            where_clauses.join(" AND "),
852            query_for_find_sort_criteria,
853            limit_field_pos,
854            offset_field_pos,
855        ),
856        span,
857    );
858
859    let query_for_find_for_update = LitStr::new(
860        &format!(
861            "SELECT {} FROM {} WHERE {} ORDER BY {} LIMIT ${} OFFSET ${} FOR UPDATE",
862            &conf.sql_select_columns,
863            table_name,
864            where_clauses.join(" AND "),
865            query_for_find_sort_criteria,
866            limit_field_pos,
867            offset_field_pos,
868        ),
869        span,
870    );
871
872    let query_for_count = LitStr::new(
873        &format!(
874            r#"SELECT count(*) as "count!" FROM {} WHERE {}"#,
875            table_name,
876            where_clauses.join(" AND "),
877        ),
878        span,
879    );
880
881    quote! {
882      impl #hub_struct {
883        pub fn select(&self) -> #select_struct {
884          #select_struct::new(self.state.clone())
885        }
886
887        pub async fn find<T: std::borrow::Borrow<#id_type>>(&self, id: T) -> sqlx::Result<#struct_name> {
888          self.select().id_eq(id.borrow()).one().await
889        }
890
891        pub async fn find_for_update<T: std::borrow::Borrow<#id_type>>(&self, id: T) -> sqlx::Result<#struct_name> {
892          self.select().id_eq(id.borrow()).one_for_update().await
893        }
894
895        pub async fn find_optional<T: std::borrow::Borrow<#id_type>>(&self, id: T) -> sqlx::Result<Option<#struct_name>> {
896          self.select().id_eq(id.borrow()).optional().await
897        }
898      }
899
900      #[sqlx_models_orm::async_trait]
901      impl sqlx_models_orm::SqlxModelHub<#struct_name> for #hub_struct {
902        fn from_state(state: #state_name) -> Self {
903          #hub_struct::new(state)
904        }
905
906        fn select(&self) -> #select_struct {
907          self.select()
908        }
909
910        async fn find(&self, id: &#id_type) -> sqlx::Result<#struct_name> {
911          self.find(id).await
912        }
913
914        async fn find_optional(&self, id: &#id_type) -> sqlx::Result<Option<#struct_name>> {
915          self.find_optional(id).await
916        }
917      }
918
919      #[derive(sqlx::Type, Debug, Copy, Clone)]
920      #[sqlx(type_name = "varchar", rename_all = "lowercase")]
921      pub enum #model_order_by {
922        #(#sort_variants,)*
923      }
924
925      #[derive(Clone)]
926      pub struct #select_struct {
927        pub state: #state_name,
928        #(pub #comparison_idents: Option<#comparison_types>,)*
929        pub order_by: Option<#model_order_by>,
930        pub desc: bool,
931        pub limit: Option<i64>,
932        pub offset: Option<i64>,
933      }
934
935      impl std::fmt::Debug for #select_struct {
936        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
937          f.debug_struct(#select_struct_str)
938           .field("order_by", &self.order_by)
939           .field("desc", &self.desc)
940           .field("limit", &self.limit)
941           .field("offset", &self.offset)
942            #(.field(#comparison_idents_as_str, &self.#comparison_idents))*
943           .finish()
944        }
945      }
946
947      impl #select_struct {
948        pub fn new(state: #state_name) -> Self {
949          Self {
950            state,
951            order_by: None,
952            desc: false,
953            limit: None,
954            offset: None,
955            #(#comparison_idents: None,)*
956          }
957        }
958
959        pub fn order_by(mut self, val: #model_order_by) -> Self {
960          self.order_by = Some(val.clone());
961          self
962        }
963
964        pub fn maybe_order_by(mut self, val: Option<#model_order_by>) -> Self {
965          self.order_by = val.clone();
966          self
967        }
968
969        pub fn desc(mut self, val: bool) -> Self {
970          self.desc = val;
971          self
972        }
973
974        pub fn limit(mut self, val: i64) -> Self {
975          self.limit = Some(val);
976          self
977        }
978
979        pub fn offset(mut self, val: i64) -> Self {
980          self.offset = Some(val);
981          self
982        }
983
984        #(
985          pub fn #builder_method_simple_idents<T: std::borrow::Borrow<#builder_method_simple_types>>(mut self, val: T) -> Self {
986            self.#builder_method_simple_idents = Some(val.borrow().to_owned());
987            self
988          }
989        )*
990
991        #(
992          pub fn #builder_method_string_idents<P: AsRef<str>>(mut self, val: P) -> Self
993          {
994            self.#builder_method_string_idents = Some(val.as_ref().into());
995            self
996          }
997        )*
998
999        pub fn use_struct(mut self, value: #select_attrs_struct) -> Self {
1000          #(self.#comparison_idents = value.#comparison_idents;)*
1001          self.order_by = value.order_by;
1002          self.desc = value.desc;
1003          self.limit = value.limit;
1004          self.offset = value.offset;
1005          self
1006        }
1007
1008        pub async fn all(&self) -> sqlx::Result<Vec<#struct_name>> {
1009          let attrs = self.state.db.fetch_all(sqlx::query_as!(#attrs_struct, #query_for_find, #(#args),*)).await?;
1010          Ok(attrs.into_iter().map(|a| self.resource(a) ).collect())
1011        }
1012
1013        pub async fn all_for_update(&self) -> sqlx::Result<Vec<#struct_name>> {
1014          let attrs = self.state.db.fetch_all(sqlx::query_as!(#attrs_struct, #query_for_find_for_update, #(#args),*)).await?;
1015          Ok(attrs.into_iter().map(|a| self.resource(a) ).collect())
1016        }
1017
1018        pub async fn count(&self) -> sqlx::Result<i64> {
1019          self.state.db.fetch_one_scalar(sqlx::query_scalar!(#query_for_count, #(#args_for_count),*)).await
1020        }
1021
1022        pub async fn one(&self) -> sqlx::Result<#struct_name> {
1023          let attrs = self.state.db.fetch_one(sqlx::query_as!(#attrs_struct, #query_for_find, #(#args),*)).await?;
1024          Ok(self.resource(attrs))
1025        }
1026
1027        pub async fn one_for_update(&self) -> sqlx::Result<#struct_name> {
1028          let attrs = self.state.db.fetch_one(sqlx::query_as!(#attrs_struct, #query_for_find_for_update, #(#args),*)).await?;
1029          Ok(self.resource(attrs))
1030        }
1031
1032        pub async fn optional(&self) -> sqlx::Result<Option<#struct_name>> {
1033          let attrs = self.state.db.fetch_optional(sqlx::query_as!(#attrs_struct, #query_for_find, #(#args),*)).await?;
1034          Ok(attrs.map(|a| self.resource(a)))
1035        }
1036
1037        fn resource(&self, attrs: #attrs_struct) -> #struct_name {
1038          #struct_name::new(self.state.clone(), attrs)
1039        }
1040      }
1041
1042      #[sqlx_models_orm::async_trait]
1043      impl sqlx_models_orm::SqlxSelectModelHub<#struct_name> for #select_struct {
1044        fn from_state(state: #state_name) -> Self {
1045          #select_struct::new(state)
1046        }
1047
1048        fn order_by(mut self, val: #model_order_by) -> Self {
1049          self.order_by(val)
1050        }
1051
1052        fn maybe_order_by(mut self, val: Option<#model_order_by>) -> Self {
1053          self.maybe_order_by(val)
1054        }
1055
1056        fn desc(self, val: bool) -> Self {
1057          self.desc(val)
1058        }
1059
1060        fn limit(self, val: i64) -> Self {
1061          self.limit(val)
1062        }
1063
1064        fn offset(self, val: i64) -> Self {
1065          self.offset(val)
1066        }
1067
1068        fn use_struct(self, value: #select_attrs_struct) -> Self {
1069          self.use_struct(value)
1070        }
1071
1072        async fn all(&self) -> sqlx::Result<Vec<#struct_name>> {
1073          self.all().await
1074        }
1075
1076        async fn count(&self) -> sqlx::Result<i64> {
1077          self.count().await
1078        }
1079
1080        async fn one(&self) -> sqlx::Result<#struct_name> {
1081          self.one().await
1082        }
1083
1084        async fn optional(&self) -> sqlx::Result<Option<#struct_name>> {
1085          self.optional().await
1086        }
1087      }
1088
1089      #[derive(Debug, Default)]
1090      pub struct #select_attrs_struct {
1091        #(pub #comparison_idents: Option<#comparison_types>,)*
1092        pub order_by: Option<#model_order_by>,
1093        pub desc: bool,
1094        pub limit: Option<i64>,
1095        pub offset: Option<i64>,
1096      }
1097    }
1098}
1099
1100fn build_queries(conf: &SqlxModelConf) -> Vec<TokenStream2> {
1101    let state_name = &conf.state_name;
1102    let struct_name = &conf.struct_name;
1103    let hub_struct = &conf.hub_struct;
1104    let table_name = &conf.table_name;
1105    let attrs_struct = &conf.attrs_struct;
1106    let span = conf.struct_name.span();
1107
1108    conf.queries.iter().map(|q|{
1109    let method_name = q.method_name.clone();
1110    let sql = q.sql.clone();
1111    let args = q.args.clone();
1112    let arg_names: Vec<Ident> = q.args.iter().map(|i| i.name.clone().unwrap().0 ).collect();
1113    let arg_types: Vec<Type> = q.args.iter().map(|i| i.ty.clone() ).collect();
1114    let query_struct_name = Ident::new(&method_name.to_string().to_case(Case::UpperCamel), q.method_name.span());
1115
1116    let query = LitStr::new(&format!(
1117      "SELECT {} FROM {} WHERE {}",
1118      &conf.sql_select_columns,
1119      table_name,
1120      sql.value()
1121    ), span);
1122
1123    let query_for_count = LitStr::new(&format!(
1124      r#"SELECT count(*) as "count!" FROM (SELECT 1 FROM {} WHERE {})"#,
1125      table_name,
1126      sql.value()
1127    ), span);
1128
1129    quote!{
1130      pub struct #query_struct_name {
1131        state: #state_name,
1132        #args
1133      }
1134
1135      impl #query_struct_name {
1136        fn init(&self, attrs: #attrs_struct) -> #struct_name {
1137          #struct_name::new(self.state.clone(), attrs)
1138        }
1139
1140        pub async fn all(&self) -> sqlx::Result<Vec<#struct_name>> {
1141          let attrs = self.state.db.fetch_all(sqlx::query_as!(#attrs_struct, #query, #(&self.#arg_names as &#arg_types),*)).await?;
1142          Ok(attrs.into_iter().map(|a| self.init(a) ).collect())
1143        }
1144
1145        pub async fn one(&self) -> sqlx::Result<#struct_name> {
1146          let attrs = self.state.db.fetch_one(sqlx::query_as!(#attrs_struct, #query, #(&self.#arg_names as &#arg_types),*)).await?;
1147          Ok(self.init(attrs))
1148        }
1149
1150        pub async fn optional(&self) -> sqlx::Result<Option<#struct_name>> {
1151          let attrs = self.state.db.fetch_optional(sqlx::query_as!(#attrs_struct, #query, #(&self.#arg_names as &#arg_types),*)).await?;
1152          Ok(attrs.map(|a| self.init(a)))
1153        }
1154
1155        pub async fn count(&self) -> sqlx::Result<i64> {
1156          self.state.db.fetch_one_scalar(sqlx::query_scalar!(#query_for_count, #(&self.#arg_names as &#arg_types),*)).await
1157        }
1158      }
1159
1160      impl #hub_struct {
1161        #[allow(clippy::too_many_arguments)]
1162        pub fn #method_name(&self, #args) -> #query_struct_name {
1163          #query_struct_name{ state: self.state.clone(), #(#arg_names,)* }
1164        }
1165      }
1166    }
1167  }).collect()
1168}
1169
1170fn build_insert(conf: &SqlxModelConf) -> TokenStream2 {
1171    let span = conf.struct_name.span();
1172    let state_name = &conf.state_name;
1173    let struct_name = &conf.struct_name;
1174    let hub_struct = &conf.hub_struct;
1175    let table_name = &conf.table_name;
1176    let attrs_struct = &conf.attrs_struct;
1177    let extra_struct_attributes = &conf.extra_struct_attributes;
1178
1179    let fields_for_insert: Vec<Field> = conf
1180        .fields
1181        .clone()
1182        .into_iter()
1183        .filter(|field| {
1184            match field
1185                .attrs
1186                .iter()
1187                .find(|a| a.path == parse_str("sqlx_model_hints").unwrap())
1188            {
1189                None => true,
1190                Some(found) => {
1191                    let hint: ModelHints = found.parse_args().unwrap();
1192                    !hint.default
1193                }
1194            }
1195        })
1196        .collect();
1197
1198    let fields_for_insert_idents: Vec<Ident> = fields_for_insert
1199        .iter()
1200        .map(|i| i.ident.as_ref().unwrap().clone())
1201        .collect();
1202    let fields_for_insert_types: Vec<Type> =
1203        fields_for_insert.iter().map(|i| i.ty.clone()).collect();
1204
1205    let fields_for_insert_as_string: Vec<LitStr> = fields_for_insert_idents
1206        .iter()
1207        .map(|i| LitStr::new(&i.to_string(), i.span()))
1208        .collect();
1209
1210    let fields_for_insert_attrs: Vec<Vec<Attribute>> = fields_for_insert
1211        .clone()
1212        .into_iter()
1213        .map(|field| {
1214            field
1215                .attrs
1216                .into_iter()
1217                .filter(|a| a.path != parse_str("sqlx_model_hints").unwrap())
1218                .collect::<Vec<Attribute>>()
1219        })
1220        .collect();
1221
1222    let insert_struct = format_ident!("Insert{}Hub", &struct_name);
1223    let insert_struct_as_string = LitStr::new(&insert_struct.to_string(), span);
1224    let insert_attrs_struct = format_ident!("Insert{}", &struct_name);
1225
1226    let column_names_to_insert = fields_for_insert_idents
1227        .iter()
1228        .map(|f| f.to_string())
1229        .collect::<Vec<String>>()
1230        .join(", \n");
1231
1232    let column_names_to_insert_positions = fields_for_insert
1233        .iter()
1234        .enumerate()
1235        .map(|(n, _)| format!("${}", n + 1))
1236        .collect::<Vec<String>>()
1237        .join(", ");
1238
1239    let query_for_insert = LitStr::new(
1240        &format!(
1241            "INSERT INTO {} ({}) VALUES ({}) RETURNING {}",
1242            table_name,
1243            column_names_to_insert,
1244            column_names_to_insert_positions,
1245            &conf.sql_select_columns,
1246        ),
1247        span,
1248    );
1249
1250    let query_for_insert_no_conflict = LitStr::new(
1251        &format!(
1252    "INSERT INTO {} ({}) VALUES ({}) ON CONFLICT (id) DO UPDATE SET id = {}.id RETURNING {}",
1253    table_name,
1254    column_names_to_insert,
1255    column_names_to_insert_positions,
1256    table_name,
1257    &conf.sql_select_columns,
1258  ),
1259        span,
1260    );
1261
1262    quote! {
1263      impl #hub_struct {
1264        #[must_use = "don't forget to save your insert"]
1265        pub fn insert(&self, attrs: #insert_attrs_struct) -> #insert_struct {
1266          #insert_struct::new(self.state.clone(), attrs)
1267        }
1268      }
1269
1270      #[derive(Clone)]
1271      #[must_use="don't forget to save() your insert"]
1272      pub struct #insert_struct {
1273        pub state: #state_name,
1274        pub attrs: #insert_attrs_struct,
1275      }
1276
1277      impl #insert_struct {
1278        pub fn new(
1279          state: #state_name,
1280          attrs: #insert_attrs_struct
1281        ) -> Self {
1282          Self{ state, attrs }
1283        }
1284
1285        #(
1286          pub fn #fields_for_insert_idents(&self) -> &#fields_for_insert_types {
1287            &self.attrs.#fields_for_insert_idents
1288          }
1289        )*
1290
1291        pub fn use_struct(mut self, attrs: #insert_attrs_struct) -> Self {
1292          self.attrs = attrs;
1293          self
1294        }
1295
1296        pub async fn save(self) -> std::result::Result<#struct_name, sqlx::Error> {
1297          let attrs = self.state.db.fetch_one(
1298            sqlx::query_as!(
1299              #attrs_struct,
1300              #query_for_insert,
1301              #(&self.attrs.#fields_for_insert_idents as &#fields_for_insert_types),*
1302            )
1303          ).await?;
1304
1305          Ok(#struct_name::new(self.state.clone(), attrs))
1306        }
1307
1308        pub async fn save_no_conflict(self) -> std::result::Result<#struct_name, sqlx::Error> {
1309          let attrs = self.state.db.fetch_one(
1310            sqlx::query_as!(
1311              #attrs_struct,
1312              #query_for_insert_no_conflict,
1313              #(&self.attrs.#fields_for_insert_idents as &#fields_for_insert_types),*
1314            )
1315          ).await?;
1316
1317          Ok(#struct_name::new(self.state.clone(), attrs))
1318        }
1319      }
1320
1321      impl std::fmt::Debug for #insert_struct {
1322        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1323          f.debug_struct(#insert_struct_as_string)
1324            #(
1325              .field(#fields_for_insert_as_string, &self.attrs.#fields_for_insert_idents)
1326            )*
1327           .finish()
1328        }
1329      }
1330
1331      #(#extra_struct_attributes)*
1332      #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1333      pub struct #insert_attrs_struct {
1334        #(
1335          #(#fields_for_insert_attrs)*
1336          pub #fields_for_insert_idents: #fields_for_insert_types,
1337        )*
1338      }
1339    }
1340}
1341
1342fn build_update(conf: &SqlxModelConf) -> TokenStream2 {
1343    let span = conf.struct_name.span();
1344    let state_name = &conf.state_name;
1345    let struct_name = &conf.struct_name;
1346    let table_name = &conf.table_name;
1347    let attrs_struct = &conf.attrs_struct;
1348    let fields = &conf.fields;
1349    let field_idents = &conf.field_idents;
1350    let id_type = &conf.id_type;
1351    let field_types: Vec<Type> = fields.clone().into_iter().map(|i| i.ty).collect();
1352
1353    let update_struct = format_ident!("Update{}Hub", &struct_name);
1354    let update_attrs_struct = format_ident!("Update{}", &struct_name);
1355
1356    let mut args_for_update = vec![];
1357
1358    for field in fields.clone().into_iter() {
1359        let ty = field.ty;
1360        let ident = field.ident.unwrap();
1361        if let Type::Path(TypePath {
1362            path: Path { ref segments, .. },
1363            ..
1364        }) = ty
1365        {
1366            args_for_update.push(quote! { &self.attrs.#ident.is_some() as &bool });
1367            if &segments[0].ident.to_string() == "Option" {
1368                args_for_update.push(quote! { &self.attrs.#ident.clone().flatten() as &#ty });
1369            } else {
1370                args_for_update.push(quote! { &self.attrs.#ident as &Option<#ty> });
1371            };
1372        }
1373    }
1374
1375    let column_names_to_insert = field_idents
1376        .iter()
1377        .map(|f| f.to_string())
1378        .collect::<Vec<String>>()
1379        .join(", \n");
1380
1381    let column_names_to_update_positions = field_idents
1382        .iter()
1383        .enumerate()
1384        .map(|(n, f)| {
1385            let base_pos = 2 + (n * 2);
1386            format!(
1387                "(CASE ${}::boolean WHEN TRUE THEN ${} ELSE {} END)",
1388                base_pos,
1389                base_pos + 1,
1390                f.clone()
1391            )
1392        })
1393        .collect::<Vec<String>>()
1394        .join(", ");
1395
1396    let query_for_update = LitStr::new(
1397        &format!(
1398            "UPDATE {} SET ({}) = ({}) WHERE id = $1 RETURNING {}",
1399            table_name,
1400            column_names_to_insert,
1401            column_names_to_update_positions,
1402            &conf.sql_select_columns,
1403        ),
1404        span,
1405    );
1406
1407    quote! {
1408      impl #struct_name {
1409        #[must_use = "don't forget to save your update"]
1410        pub fn update(self) -> #update_struct {
1411          #update_struct::new(self.state, self.attrs.id)
1412        }
1413      }
1414
1415      #[must_use = "don't forget to save your update"]
1416      pub struct #update_struct {
1417        pub state: #state_name,
1418        pub attrs: #update_attrs_struct,
1419        pub id: #id_type,
1420      }
1421
1422      impl #update_struct {
1423        pub fn new(state: #state_name, id: #id_type) -> Self {
1424          Self{ state, id, attrs: Default::default() }
1425        }
1426
1427        #(
1428          pub fn #field_idents(mut self, val: #field_types) -> Self {
1429            self.attrs.#field_idents = Some(val);
1430            self
1431          }
1432        )*
1433
1434        pub fn use_struct(mut self, value: #update_attrs_struct) -> Self {
1435          self.attrs = value;
1436          self
1437        }
1438
1439        pub async fn save(self) -> std::result::Result<#struct_name, sqlx::Error> {
1440          let attrs = self.state.db.fetch_one(
1441            sqlx::query_as!(
1442              #attrs_struct,
1443              #query_for_update,
1444              self.id,
1445              #(#args_for_update),*
1446            )
1447          ).await?;
1448
1449          Ok(#struct_name::new(self.state.clone(), attrs))
1450        }
1451      }
1452
1453      #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
1454      pub struct #update_attrs_struct {
1455        #( pub #field_idents: Option<#field_types>,)*
1456      }
1457    }
1458}
1459
1460fn build_delete(conf: &SqlxModelConf) -> TokenStream2 {
1461    let struct_name = &conf.struct_name;
1462    let table_name = &conf.table_name;
1463    let span = conf.struct_name.span();
1464
1465    let query_for_delete = LitStr::new(&format!("DELETE FROM {} WHERE id = $1", table_name), span);
1466
1467    quote! {
1468      impl #struct_name {
1469        pub async fn delete(self) -> sqlx::Result<()> {
1470          self.state.db.execute(sqlx::query!(#query_for_delete, self.attrs.id)).await?;
1471          Ok(())
1472        }
1473      }
1474    }
1475}