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
17fn 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 return_type = None;
42 sql = input.parse()?;
43 } else {
44 return_type = Some(input.parse()?);
46 input.parse::<syn::Token![,]>()?; sql = input.parse()?;
48 }
49
50 let mut args = Vec::new();
51 while !input.is_empty() {
52 input.parse::<syn::Token![,]>()?; 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 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 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 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 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(), #(#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 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 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}