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 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}