Skip to main content

lazysql_macros/
lib.rs

1use sqlformat::{FormatOptions, Indent, QueryParams, format};
2use std::{collections::HashMap, env, path::Path};
3
4use lazysql_core::utility::utils::{get_db_schema, validate_sql_syntax_with_sqlite};
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{
8    Data, DeriveInput, Fields, Ident, ItemStruct, LitStr, Type, parse_macro_input, parse_quote,
9    spanned::Spanned,
10};
11use type_inference::{
12    binding_patterns::get_type_of_binding_parameters, expr::BaseType, pg_cast_syntax_to_sqlite,
13    select_patterns::get_types_from_select, table::create_tables, validate_insert_strict,
14    validate_single_statement,
15};
16
17/// This nicely formats the sql string.
18///
19/// Useful for vscode hover over fn
20fn format_sql(sql: &str) -> String {
21    let options = FormatOptions {
22        indent: Indent::Tabs,
23        ..Default::default()
24    };
25    format(sql, &QueryParams::None, &options)
26}
27
28struct RuntimeSqlInput {
29    return_type: Option<Type>,
30    sql: syn::LitStr,
31    args: Vec<Type>,
32}
33
34impl syn::parse::Parse for RuntimeSqlInput {
35    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
36        let return_type;
37        let sql;
38
39        if input.peek(syn::LitStr) {
40            // sql_runtime!("UPDATE...", arg, arg)
41            return_type = None;
42            sql = input.parse()?;
43        } else {
44            //  sql_runtime!(UserDTO, "SELECT...", arg, arg)
45            return_type = Some(input.parse()?);
46            input.parse::<syn::Token![,]>()?; // Eat comma
47            sql = input.parse()?;
48        }
49
50        let mut args = Vec::new();
51        while !input.is_empty() {
52            input.parse::<syn::Token![,]>()?; // Eat comma
53            if input.is_empty() {
54                break;
55            }
56            args.push(input.parse()?);
57        }
58
59        Ok(RuntimeSqlInput {
60            return_type,
61            sql,
62            args,
63        })
64    }
65}
66
67fn parse_runtime_macro(ty: &syn::Type) -> syn::Result<Option<RuntimeSqlInput>> {
68    if let syn::Type::Macro(type_macro) = ty
69        && type_macro.mac.path.is_ident("sql_runtime")
70    {
71        let parsed: RuntimeSqlInput = syn::parse2(type_macro.mac.tokens.clone())?;
72        return Ok(Some(parsed));
73    }
74    Ok(None)
75}
76
77#[proc_macro_attribute]
78pub fn lazy_sql(args: TokenStream, input: TokenStream) -> TokenStream {
79    let path_lit_opt = if args.is_empty() {
80        None
81    } else {
82        match syn::parse::<syn::LitStr>(args) {
83            Ok(lit) => {
84                let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("No MANIFEST_DIR");
85                let full_path = Path::new(&manifest_dir).join(lit.value());
86                let full_path_str = full_path.to_str().expect("Invalid path string");
87
88                Some(syn::LitStr::new(
89                    full_path_str,
90                    proc_macro2::Span::call_site(),
91                ))
92            }
93            Err(_) => {
94                let err = syn::Error::new(
95                    proc_macro2::Span::call_site(),
96                    "lazy_sql requires either no arguments or a path string to a sql/db file.",
97                );
98                let err_tokens = err.to_compile_error();
99                let input_tokens = proc_macro2::TokenStream::from(input);
100                return quote! {
101                    #err_tokens
102                    #input_tokens
103                }
104                .into();
105            }
106        }
107    };
108
109    let mut item_struct = parse_macro_input!(input as ItemStruct);
110
111    match expand(&mut item_struct, path_lit_opt.as_ref()) {
112        Ok(output) => {
113            let watcher = if let Some(abs_path) = path_lit_opt {
114                quote! {
115                    const _: &[u8] = include_bytes!(#abs_path);
116                }
117            } else {
118                quote! {}
119            };
120
121            let final_output = quote! {
122                #output
123                #watcher
124            };
125
126            final_output.into()
127        }
128        Err(err) => err.to_compile_error().into(),
129    }
130}
131
132fn expand(
133    item_struct: &mut ItemStruct,
134    db_path_lit: Option<&syn::LitStr>,
135) -> syn::Result<proc_macro2::TokenStream> {
136    let mut all_tables = HashMap::new();
137
138    if let Some(path) = db_path_lit {
139        let db_path = path.value();
140        let schemas = get_db_schema(&db_path).map_err(|err| {
141            syn::Error::new(
142                db_path_lit.span(),
143                format!("Failed to load DB schema: {}", err),
144            )
145        })?;
146        for schema in schemas {
147            create_tables(&schema, &mut all_tables);
148        }
149    }
150
151    let struct_name = &item_struct.ident;
152
153    let fields = match &mut item_struct.fields {
154        syn::Fields::Named(named) => named,
155        _ => {
156            return Err(syn::Error::new(
157                item_struct.span(),
158                "lazy_sql requires a struct with named fields",
159            ));
160        }
161    };
162
163    let mut sql_assignments = Vec::new();
164    let mut standard_assignments = Vec::new();
165    let mut standard_params = Vec::new();
166    let mut generated_methods = Vec::new();
167    let mut generated_structs = Vec::new();
168    let mut re_exports = Vec::new();
169
170    for field in fields.named.iter_mut() {
171        let ident = field.ident.as_ref().unwrap();
172        let field_attrs = &field.attrs;
173
174        // Check if type is sql!("...")
175        if let Some(sql_lit) = parse_sql_macro_type(&field.ty)? {
176            let sql_query = pg_cast_syntax_to_sqlite(&sql_lit.value());
177
178            if !validate_single_statement(&sql_query) {
179                return Err(syn::Error::new(
180                    sql_lit.span(),
181                    "Multiple SQL statements detected. \
182                     Please split them into separate struct fields.",
183                ));
184            }
185
186            let transpiled_sql_lit = syn::LitStr::new(&sql_query, sql_lit.span());
187
188            if let Err(err_msg) = validate_sql_syntax_with_sqlite(&all_tables, &sql_query) {
189                return Err(syn::Error::new(sql_lit.span(), err_msg.to_string()));
190            }
191
192            if let Err(err_msg) = validate_insert_strict(&sql_query, &all_tables) {
193                return Err(syn::Error::new(sql_lit.span(), err_msg.to_string()));
194            }
195
196            if sql_query.trim().to_uppercase().starts_with("CREATE TABLE") {
197                create_tables(&sql_query, &mut all_tables);
198
199                field.ty = parse_quote!(lazysql::internal_sqlite::lazy_statement::LazyStmt);
200                sql_assignments.push(quote! {
201                    #ident: lazysql::internal_sqlite::lazy_statement::LazyStmt {
202                        sql_query: #transpiled_sql_lit,
203                        stmt: std::ptr::null_mut(),
204                    }
205                });
206
207                let doc_comment = format!(" \n**SQL**\n```sql\n{}", format_sql(&sql_query));
208                generated_methods.push(quote! {
209                    #(#field_attrs)*
210                    #[doc = #doc_comment]
211                    pub fn #ident(&mut self) -> Result<(), lazysql::errors::SqlWriteError> {
212                        if self.#ident.stmt.is_null() {
213                            unsafe {
214                                lazysql::utility::utils::prepare_stmt(
215                                    self.__db.db,
216                                    &mut self.#ident.stmt,
217                                    self.#ident.sql_query
218                                )?;
219                            }
220                        }
221                        let mut preparred_statement = lazysql::internal_sqlite::preparred_statement::PreparredStmt {
222                            stmt: self.#ident.stmt,
223                            conn: self.__db.db,
224                        };
225                        preparred_statement.step()?;
226                        Ok(())
227                    }
228                });
229                continue;
230            }
231
232            let select_types = match get_types_from_select(&sql_query, &all_tables) {
233                Ok(types) => types,
234                Err(err_msg) => {
235                    return Err(syn::Error::new(
236                        sql_lit.span(),
237                        format!("Return Type Error: {}", err_msg),
238                    ));
239                }
240            };
241
242            let binding_types = match get_type_of_binding_parameters(&sql_query, &all_tables) {
243                Ok(types) => types,
244                Err(err) => {
245                    let lines: Vec<&str> = sql_query.lines().collect();
246                    let line_idx = err.start.line.saturating_sub(1) as usize;
247                    let start_col = err.start.column.saturating_sub(1) as usize;
248                    let end_col = err.end.column.saturating_sub(1) as usize;
249                    let mut msg = err.message.to_string();
250                    if let Some(raw_line) = lines.get(line_idx) {
251                        let indent_len_bytes = raw_line
252                            .char_indices()
253                            .take_while(|(_, c)| c.is_whitespace())
254                            .last()
255                            .map(|(i, c)| i + c.len_utf8())
256                            .unwrap_or(0);
257                        let start_byte_idx = raw_line
258                            .chars()
259                            .take(start_col)
260                            .map(|c| c.len_utf8())
261                            .sum::<usize>();
262                        let end_byte_idx = raw_line
263                            .chars()
264                            .take(end_col)
265                            .map(|c| c.len_utf8())
266                            .sum::<usize>();
267                        let safe_indent = if indent_len_bytes <= start_byte_idx {
268                            indent_len_bytes
269                        } else {
270                            0
271                        };
272                        let trimmed_line = &raw_line[safe_indent..];
273                        let err_start_in_trimmed = start_byte_idx - safe_indent;
274                        let err_len = end_byte_idx - start_byte_idx;
275                        let padding: String = trimmed_line[..err_start_in_trimmed]
276                            .chars()
277                            .map(|c| if c == '\t' { '\t' } else { ' ' })
278                            .collect();
279                        let arrows = "^".repeat(err_len.max(1));
280                        msg = format!("{}\n\n{}\n{}{}", msg, trimmed_line, padding, arrows);
281                    }
282                    return Err(syn::Error::new(sql_lit.span(), msg));
283                }
284            };
285
286            let formated_sql_query = format_sql(&sql_query);
287            let doc_comment = format!(" \n**SQL**\n```sql\n{}", formated_sql_query);
288
289            field.ty = parse_quote!(lazysql::internal_sqlite::lazy_statement::LazyStmt);
290
291            sql_assignments.push(quote! {
292                #ident: lazysql::internal_sqlite::lazy_statement::LazyStmt {
293                    sql_query: #transpiled_sql_lit,
294                    stmt: std::ptr::null_mut(),
295                }
296            });
297
298            if select_types.is_empty() && binding_types.is_empty() {
299                generated_methods.push(quote! {
300                    #(#field_attrs)*
301                    #[doc = #doc_comment]
302                    pub fn #ident(&mut self) -> Result<(), lazysql::errors::SqlWriteError> {
303                        if self.#ident.stmt.is_null() {
304                            unsafe {
305                                lazysql::utility::utils::prepare_stmt(
306                                    self.__db.db,
307                                    &mut self.#ident.stmt,
308                                    self.#ident.sql_query
309                                )?;
310                            }
311                        }
312                        let mut preparred_statement = lazysql::internal_sqlite::preparred_statement::PreparredStmt {
313                            stmt: self.#ident.stmt,
314                            conn: self.__db.db,
315                        };
316                        preparred_statement.step()?;
317                        Ok(())
318                    }
319                });
320            } else if select_types.is_empty() && !binding_types.is_empty() {
321                let mut method_args = Vec::new();
322                let mut bind_calls = Vec::new();
323
324                for (i, bind_type) in binding_types.iter().enumerate() {
325                    let arg_name = quote::format_ident!("arg_{}", i);
326                    let bind_index = (i + 1) as i32;
327
328                    let rust_base_type = match bind_type.base_type {
329                        BaseType::Integer => quote! { i64 },
330                        BaseType::Real => quote! { f64 },
331                        BaseType::Bool => quote! { bool },
332                        BaseType::Text => quote! { &str },
333                        _ => quote! {},
334                    };
335
336                    let final_type = if bind_type.nullable {
337                        quote! { Option<#rust_base_type> }
338                    } else {
339                        quote! { #rust_base_type }
340                    };
341
342                    method_args.push(quote! { #arg_name: #final_type });
343
344                    bind_calls.push(quote! {
345                        preparred_statement.bind_parameter(#bind_index, #arg_name)?;
346                    });
347                }
348
349                generated_methods.push(quote! {
350                    #(#field_attrs)*
351                    #[doc = #doc_comment]
352                    pub fn #ident(&mut self, #(#method_args),*) -> Result<(), lazysql::errors::SqlWriteBindingError> {
353                        if self.#ident.stmt.is_null() {
354                            unsafe {
355                                lazysql::utility::utils::prepare_stmt(
356                                    self.__db.db,
357                                    &mut self.#ident.stmt,
358                                    self.#ident.sql_query
359                                )?;
360                            }
361                        }
362
363                        let mut preparred_statement = lazysql::internal_sqlite::preparred_statement::PreparredStmt {
364                            stmt: self.#ident.stmt,
365                            conn: self.__db.db,
366                        };
367
368                        #(#bind_calls)*
369
370                        preparred_statement.step()?;
371
372                        Ok(())
373                    }
374                });
375            } else if !select_types.is_empty() && binding_types.is_empty() {
376                let method_name = ident.to_string();
377                let pascal_name: String = method_name
378                    .split('_')
379                    .map(|s| {
380                        let mut c = s.chars();
381                        match c.next() {
382                            None => String::new(),
383                            Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
384                        }
385                    })
386                    .collect();
387
388                let struct_name = quote::format_ident!("{}", pascal_name);
389                let mapper_struct_name = quote::format_ident!("{}_", pascal_name);
390
391                re_exports.push(struct_name.clone());
392
393                let mut struct_fields = Vec::new();
394
395                for col in select_types.iter() {
396                    let name = quote::format_ident!("{}", col.name);
397
398                    let base_ty = match col.data_type.base_type {
399                        BaseType::Integer => quote! { i64 },
400                        BaseType::Real => quote! { f64 },
401                        BaseType::Text => quote! { String },
402                        BaseType::Bool => quote! { bool },
403                        _ => quote! {},
404                    };
405
406                    let final_ty = if col.data_type.nullable {
407                        quote! { Option<#base_ty> }
408                    } else {
409                        quote! { #base_ty }
410                    };
411
412                    struct_fields.push(quote! { pub #name: #final_ty });
413                }
414
415                generated_structs.push(quote! {
416                    #[derive(Clone, Debug, lazysql::SqlMapping)]
417                    pub struct #struct_name {
418                        #(#struct_fields),*
419                    }
420                });
421
422                generated_methods.push(quote! {
423                    #(#field_attrs)*
424                #[doc = #doc_comment]
425                pub fn #ident(&mut self) -> Result<lazysql::internal_sqlite::rows_dao::Rows<'_, #mapper_struct_name>, lazysql::errors::SqlReadError> {
426                        if self.#ident.stmt.is_null() {
427                            unsafe {
428                                lazysql::utility::utils::prepare_stmt(
429                                    self.__db.db,
430                                    &mut self.#ident.stmt,
431                                    self.#ident.sql_query
432                                )?;
433                            }
434                        }
435
436            let preparred_statement = lazysql::internal_sqlite::preparred_statement::PreparredStmt {
437                stmt: self.#ident.stmt,
438                conn: self.__db.db,
439            };
440            Ok(preparred_statement.query(#struct_name))
441        }
442    });
443            } else {
444                let method_name = ident.to_string();
445                let pascal_name: String = method_name
446                    .split('_')
447                    .map(|s| {
448                        let mut c = s.chars();
449                        match c.next() {
450                            None => String::new(),
451                            Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
452                        }
453                    })
454                    .collect();
455
456                let output_struct_name = quote::format_ident!("{}", pascal_name);
457                let mapper_struct_name = quote::format_ident!("{}_", pascal_name);
458
459                re_exports.push(output_struct_name.clone());
460
461                let mut struct_fields = Vec::new();
462
463                for col in select_types.iter() {
464                    let name = quote::format_ident!("{}", col.name);
465
466                    let base_ty = match col.data_type.base_type {
467                        BaseType::Integer => quote! { i64 },
468                        BaseType::Real => quote! { f64 },
469                        BaseType::Text => quote! { String },
470                        BaseType::Bool => quote! { bool },
471                        _ => quote! {},
472                    };
473
474                    let final_ty = if col.data_type.nullable {
475                        quote! { Option<#base_ty> }
476                    } else {
477                        quote! { #base_ty }
478                    };
479
480                    struct_fields.push(quote! { pub #name: #final_ty });
481                }
482
483                generated_structs.push(quote! {
484                    #[derive(Clone, Debug, lazysql::SqlMapping)]
485                    pub struct #output_struct_name {
486                        #(#struct_fields),*
487                    }
488                });
489
490                let mut method_args = Vec::new();
491                let mut bind_calls = Vec::new();
492
493                for (i, bind_type) in binding_types.iter().enumerate() {
494                    let arg_name = quote::format_ident!("arg_{}", i);
495                    let bind_index = (i + 1) as i32;
496
497                    let rust_base_type = match bind_type.base_type {
498                        BaseType::Integer => quote! { i64 },
499                        BaseType::Real => quote! { f64 },
500                        BaseType::Bool => quote! { bool },
501                        BaseType::Text => quote! { &str },
502                        _ => quote! {},
503                    };
504
505                    let final_type = if bind_type.nullable {
506                        quote! { Option<#rust_base_type> }
507                    } else {
508                        quote! { #rust_base_type }
509                    };
510
511                    method_args.push(quote! { #arg_name: #final_type });
512
513                    bind_calls.push(quote! {
514                        preparred_statement.bind_parameter(#bind_index, #arg_name)?;
515                    });
516                }
517
518                generated_methods.push(quote! {
519                    #(#field_attrs)*
520                    #[doc = #doc_comment]
521                    pub fn #ident(&mut self, #(#method_args),*) -> Result<lazysql::internal_sqlite::rows_dao::Rows<'_, #mapper_struct_name>, lazysql::errors::SqlReadErrorBindings> {
522                        if self.#ident.stmt.is_null() {
523                            unsafe {
524                                lazysql::utility::utils::prepare_stmt(
525                                    self.__db.db,
526                                    &mut self.#ident.stmt,
527                                    self.#ident.sql_query
528                                )?;
529                            }
530                        }
531
532                        let mut preparred_statement = lazysql::internal_sqlite::preparred_statement::PreparredStmt {
533                            stmt: self.#ident.stmt,
534                            conn: self.__db.db,
535                        };
536
537                        #(#bind_calls)*
538
539                        Ok(preparred_statement.query(#output_struct_name))
540                    }
541                });
542            }
543        } else if let Some(runtime_input) = parse_runtime_macro(&field.ty)? {
544            let sql_lit = runtime_input.sql;
545            let sql_query = pg_cast_syntax_to_sqlite(&sql_lit.value());
546
547            let transpiled_sql_lit = syn::LitStr::new(&sql_query, sql_lit.span());
548
549            field.ty = parse_quote!(lazysql::internal_sqlite::lazy_statement::LazyStmt);
550
551            sql_assignments.push(quote! {
552                #ident: lazysql::internal_sqlite::lazy_statement::LazyStmt {
553                    sql_query: #transpiled_sql_lit,
554                    stmt: std::ptr::null_mut(),
555                }
556            });
557
558            let mut method_args = Vec::new();
559            let mut bind_calls = Vec::new();
560
561            for (i, arg_type) in runtime_input.args.iter().enumerate() {
562                let arg_name = quote::format_ident!("arg_{}", i);
563                let bind_index = (i + 1) as i32;
564
565                method_args.push(quote! { #arg_name: #arg_type });
566
567                bind_calls.push(quote! {
568                    preparred_statement.bind_parameter(#bind_index, #arg_name)?;
569                });
570            }
571
572            let doc_comment = format!(" \n**SQL**\n```sql\n{}", format_sql(&sql_lit.value()));
573
574            if let Some(ret_type) = runtime_input.return_type {
575                let mapper_type = if let syn::Type::Path(type_path) = &ret_type {
576                    if let Some(segment) = type_path.path.segments.last() {
577                        let type_name = segment.ident.to_string();
578                        let primitives = [
579                            "i64", "i32", "u64", "u32", "f64", "f32", "bool", "String", "Option",
580                        ];
581
582                        if primitives.iter().any(|&p| type_name.starts_with(p)) {
583                            quote! { #ret_type }
584                        } else {
585                            let new_ident = quote::format_ident!("{}_", segment.ident);
586                            quote! { #new_ident }
587                        }
588                    } else {
589                        quote! { #ret_type }
590                    }
591                } else {
592                    quote! { #ret_type }
593                };
594
595                generated_methods.push(quote! {
596                    #(#field_attrs)*
597                    #[doc = #doc_comment]
598                    // SELECT
599                    pub fn #ident(&mut self, #(#method_args),*) -> Result<lazysql::internal_sqlite::rows_dao::Rows<#mapper_type>, lazysql::errors::SqlReadErrorBindings> {
600                        if self.#ident.stmt.is_null() {
601                            unsafe {
602                                lazysql::utility::utils::prepare_stmt(
603                                    self.__db.db,
604                                    &mut self.#ident.stmt,
605                                    self.#ident.sql_query
606                                )?;
607                            }
608                        }
609
610                        let mut preparred_statement = lazysql::internal_sqlite::preparred_statement::PreparredStmt {
611                            stmt: self.#ident.stmt,
612                            conn: self.__db.db,
613                        };
614
615                        #(#bind_calls)*
616
617                        Ok(preparred_statement.query(#mapper_type))
618                    }
619                });
620            } else {
621                // Non SELECT
622                generated_methods.push(quote! {
623                    #(#field_attrs)*
624                    #[doc = #doc_comment]
625                    pub fn #ident(&mut self, #(#method_args),*) -> Result<(), lazysql::errors::SqlWriteBindingError> {
626                        if self.#ident.stmt.is_null() {
627                            unsafe {
628                                lazysql::utility::utils::prepare_stmt(
629                                    self.__db.db,
630                                    &mut self.#ident.stmt,
631                                    self.#ident.sql_query
632                                )?;
633                            }
634                        }
635
636                        let mut preparred_statement = lazysql::internal_sqlite::preparred_statement::PreparredStmt {
637                            stmt: self.#ident.stmt,
638                            conn: self.__db.db,
639                        };
640
641                        #(#bind_calls)*
642
643                        preparred_statement.step()?;
644                        Ok(())
645                    }
646                });
647            }
648        }
649        // normal struct and field
650        else {
651            let ty = &field.ty;
652            standard_params.push(quote! { #ident: #ty });
653            standard_assignments.push(quote! { #ident });
654        }
655    }
656
657    fields.named.insert(
658        0,
659parse_quote! { __db: std::sync::Arc<lazysql::internal_sqlite::lazy_connection::LazyConnection> }
660,
661    );
662
663    let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();
664
665    let mod_name = quote::format_ident!(
666        "__lazy_sql_inner_{}",
667        struct_name.to_string().to_lowercase()
668    );
669
670    item_struct.vis = parse_quote!(pub);
671
672    Ok(quote! {
673        #[doc(hidden)]
674        mod #mod_name {
675            use super::*;
676            #(#generated_structs)*
677            #item_struct
678
679            impl #impl_generics #struct_name #ty_generics #where_clause {
680                    pub fn new(
681                db: impl Into<std::sync::Arc<lazysql::internal_sqlite::lazy_connection::LazyConnection>>,
682                #(#standard_params),*
683            ) -> Self {
684                Self {
685                    __db: db.into(), // Call .into() to turn it into the Arc
686                    #(#standard_assignments,)*
687                    #(#sql_assignments,)*
688                    }
689                }
690
691
692    pub fn transaction<T, F>(&mut self, f: F) -> Result<T, lazysql::errors::Error>
693    where
694        F: FnOnce(&mut Self) -> Result<T, lazysql::errors::Error>,
695    {
696        self.__db.exec("BEGIN")
697            .map_err(lazysql::errors::Error::from)?;
698
699        let result = f(self);
700
701        match result {
702            Ok(val) => {
703                if let Err(e) = self.__db.exec("COMMIT") {
704                    return Err(lazysql::errors::Error::from(e));
705                }
706                Ok(val)
707            }
708            Err(e) => {
709                // Attempt rollback, ignoring failure since we are already erroring
710                let _ = self.__db.exec("ROLLBACK");
711                Err(e)
712            }
713        }
714    }
715
716
717                #(#generated_methods)*
718            }
719        }
720
721        pub use #mod_name::#struct_name;
722    })
723}
724
725fn parse_sql_macro_type(ty: &Type) -> syn::Result<Option<LitStr>> {
726    if let Type::Macro(type_macro) = ty
727        && type_macro.mac.path.is_ident("sql")
728    {
729        let lit = syn::parse2(type_macro.mac.tokens.clone()).map_err(|_| {
730            syn::Error::new(
731                type_macro.mac.tokens.span(),
732                "sql!(...) must contain a string",
733            )
734        })?;
735
736        return Ok(Some(lit));
737    }
738
739    Ok(None)
740}
741
742#[proc_macro_derive(SqlMapping)]
743pub fn my_macro(input: TokenStream) -> TokenStream {
744    let input = parse_macro_input!(input as DeriveInput);
745
746    let struct_name = &input.ident;
747
748    let name_as_string = struct_name.to_string();
749    let new_name_string = format!("{}_", name_as_string);
750    let mapper_struct_name = Ident::new(&new_name_string, struct_name.span());
751
752    // TODO error handling
753    let fields = match &input.data {
754        Data::Struct(s) => match &s.fields {
755            Fields::Named(fields_named) => &fields_named.named,
756            _ => panic!("This macro only works on structs with named fields"),
757        },
758        _ => panic!("This macro only works on structs"),
759    };
760
761    let field_bindings = fields.iter().enumerate().map(|(i, f)| {
762        let field_name = f.ident.as_ref().unwrap();
763        let field_type = &f.ty;
764        let index = i as i32;
765
766        quote! {
767            let #field_name = unsafe
768            {
769            <#field_type as lazysql::traits::from_sql::FromSql>::from_sql(stmt, #index)
770            };
771
772        }
773    });
774
775    let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap());
776    let expanded = quote! {
777        #[derive(Clone, Debug)]
778        pub struct #mapper_struct_name;
779
780        impl lazysql::traits::row_mapper::RowMapper for #mapper_struct_name {
781            type Output = #struct_name;
782
783            unsafe fn map_row(&self, stmt: *mut lazysql::libsqlite3_sys::sqlite3_stmt) -> Self::Output {
784                #(#field_bindings)*
785
786                Self::Output {
787                    #(#field_names),*
788                }
789            }
790        }
791
792        #[allow(non_upper_case_globals)]
793        pub const #struct_name: #mapper_struct_name = #mapper_struct_name;
794    };
795
796    TokenStream::from(expanded)
797}