1use quote::quote;
2use std::collections::HashSet;
3use syn::{DeriveInput, GenericArgument, PathArguments, Type, TypePath};
4
5use crate::schemable::{FieldMeta, ParsedStruct, to_snake_case};
6use crate::typed_handles::gen_typed_handles;
7
8pub fn derive_record(
10 input: proc_macro2::TokenStream,
11 runtime_path: proc_macro2::TokenStream,
12) -> proc_macro2::TokenStream {
13 let input = match syn::parse2::<DeriveInput>(input) {
14 Ok(input) => input,
15 Err(err) => return err.to_compile_error(),
16 };
17 derive_record_impl(&input, runtime_path)
18}
19
20pub(crate) fn derive_record_impl(
22 input: &DeriveInput,
23 runtime_path: proc_macro2::TokenStream,
24) -> proc_macro2::TokenStream {
25 let parsed = match ParsedStruct::from_derive_input(input.clone()) {
26 Ok(p) => p,
27 Err(e) => return e.to_compile_error(),
28 };
29
30 let ident = &parsed.ident;
31 let mut generics = parsed.generics.clone();
32
33 let crate_path = crate::runtime_path(input, runtime_path);
34
35 gen_where_clause(&mut generics, &parsed.fields, &crate_path);
36
37 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
38 let table_name = gen_table_name(&parsed, &crate_path);
39 let table_schema = gen_table_schema(&parsed);
40 let scan_root = gen_scan_root(&parsed.fields);
41 let references = gen_references(&parsed.fields, &crate_path);
42 let field_inits = gen_field_initializers(&parsed.fields, &crate_path);
43 let field_inits_unordered = gen_field_initializers_unordered(&parsed.fields, &crate_path);
44 let column_names = gen_record_column_names(&parsed.fields, &crate_path);
45 let bind_names = gen_record_bind_column_names(&parsed.fields, &crate_path);
46 let bind_stmts = gen_bind_statements(&parsed.fields, &crate_path);
47 let bind_selected_arms = gen_bind_selected_arms(&parsed.fields, &crate_path);
48 let typed_handles = gen_typed_handles(&parsed, &crate_path);
49
50 quote! {
51 impl #impl_generics #crate_path::Record for #ident #ty_generics #where_clause {
52 fn record_schema() -> #crate_path::RecordSchema<Self> {
53 let references = {
54 let mut refs = ::std::vec::Vec::new();
55 #(#references)*
56 refs
57 };
58 let columns = {
59 let mut cols = ::std::vec::Vec::new();
60 #(#column_names)*
61 cols
62 };
63 let bind_columns = {
64 let mut cols = ::std::vec::Vec::new();
65 #(#bind_names)*
66 cols
67 };
68 #crate_path::RecordSchema::new(#table_name)
69 .schema(#table_schema)
70 .root(#scan_root)
71 .references(references)
72 .columns(columns)
73 .bind_columns(bind_columns)
74 }
75
76 fn record_bind_values(
77 &self,
78 args: &mut #crate_path::Arguments<'static>,
79 ) -> Result<(), ::sqlx::Error> {
80 #(#bind_stmts)*
81 Ok(())
82 }
83
84 fn record_bind_selected(
85 &self,
86 columns: &[&str],
87 args: &mut #crate_path::Arguments<'static>,
88 ) -> Result<(), ::sqlx::Error> {
89 for column in columns {
90 match *column {
91 #(#bind_selected_arms)*
92 other => {
93 return Err(::sqlx::Error::ColumnNotFound(other.to_string()));
94 }
95 }
96 }
97 Ok(())
98 }
99
100 fn record_scan_ordered(
101 row: &#crate_path::Row,
102 start_idx: &mut usize,
103 ) -> Result<Self, ::sqlx::Error> {
104 use ::sqlx::Row as _;
105 use ::sqlx::ValueRef as _;
106 Ok(Self {
107 #(#field_inits)*
108 })
109 }
110
111 fn record_scan_unordered(
112 row: &#crate_path::Row,
113 ) -> Result<Self, ::sqlx::Error> {
114 use ::sqlx::Row as _;
115 Ok(Self {
116 #(#field_inits_unordered)*
117 })
118 }
119 }
120
121 impl #impl_generics ::sqlx::FromRow<'_, #crate_path::Row> for #ident #ty_generics #where_clause {
122 fn from_row(row: &#crate_path::Row) -> Result<Self, ::sqlx::Error> {
123 <Self as #crate_path::Record>::record_scan(row)
124 }
125 }
126
127 #typed_handles
128 }
129}
130
131fn gen_where_clause(
133 generics: &mut syn::Generics,
134 fields: &[FieldMeta],
135 crate_path: &proc_macro2::TokenStream,
136) {
137 let mut seen = HashSet::new();
138 let wc = generics.where_clause.get_or_insert(syn::WhereClause {
139 where_token: <syn::Token![where]>::default(),
140 predicates: syn::punctuated::Punctuated::new(),
141 });
142
143 for field in fields {
144 if is_skip(field) {
145 continue;
146 }
147
148 let ty = &field.ty;
149 let ty_str = quote::quote!(#ty).to_string();
150
151 if (is_flatten(field) || is_reference(field)) && is_selectable(field) {
152 let bound_ty = option_inner_type(ty).unwrap_or(ty);
153 let bound_ty_str = quote::quote!(#bound_ty).to_string();
154 if seen.insert(bound_ty_str) {
155 wc.predicates.push(syn::parse_quote! {
156 #bound_ty: #crate_path::Record
157 });
158 }
159 } else if is_json(field) && is_selectable(field) && seen.insert(ty_str.clone()) {
160 wc.predicates.push(syn::parse_quote! {
161 #ty: ::serde::de::DeserializeOwned
162 });
163 }
164
165 if !is_bindable(field) {
166 continue;
167 }
168
169 if is_flatten(field) {
170 if seen.insert(ty_str.clone()) {
171 wc.predicates.push(syn::parse_quote! {
172 #ty: #crate_path::Record
173 });
174 }
175 } else if is_json(field) {
176 if seen.insert(format!("{ty_str}:serialize")) {
177 wc.predicates.push(syn::parse_quote! {
178 #ty: ::serde::Serialize
179 });
180 }
181 } else {
182 wc.predicates.push(syn::parse_quote! {
183 #ty: ::core::clone::Clone
184 + for<'q> ::sqlx::Encode<'q, #crate_path::Database>
185 + ::sqlx::Type<#crate_path::Database>
186 + ::core::marker::Send
187 });
188 }
189 }
190}
191
192fn gen_field_initializers(
194 fields: &[FieldMeta],
195 crate_path: &proc_macro2::TokenStream,
196) -> Vec<proc_macro2::TokenStream> {
197 let mut inits = Vec::with_capacity(fields.len());
198
199 for field in fields {
200 let Some(ident) = &field.ident else {
201 continue;
202 };
203
204 let init = if is_skip(field) || !is_selectable(field) {
205 gen_default_init(ident)
206 } else if is_reference(field) && is_option(&field.ty) {
207 gen_optional_reference_init(ident, &field.ty, crate_path)
208 } else if is_flatten(field) || is_reference(field) {
209 gen_flatten_init(
210 ident,
211 option_inner_type(&field.ty).unwrap_or(&field.ty),
212 crate_path,
213 )
214 } else if is_json(field) {
215 gen_json_init(ident, crate_path)
216 } else {
217 gen_scalar_init(ident)
218 };
219
220 inits.push(init);
221 }
222
223 inits
224}
225
226fn gen_table_name(
227 parsed: &ParsedStruct,
228 crate_path: &proc_macro2::TokenStream,
229) -> proc_macro2::TokenStream {
230 if let Some(name) = parsed
231 .container
232 .name
233 .as_ref()
234 .or(parsed.container.table.as_ref())
235 {
236 let value = name.value();
237 return quote! { #value };
238 }
239
240 if let Some(field) = parsed
241 .fields
242 .iter()
243 .find(|field| is_flatten(field) || is_reference(field))
244 {
245 let ty = option_inner_type(&field.ty).unwrap_or(&field.ty);
246 return quote! { <#ty as #crate_path::Record>::record_table_name() };
247 }
248
249 let value = to_snake_case(&parsed.ident.to_string());
250 quote! { #value }
251}
252
253fn gen_table_schema(parsed: &ParsedStruct) -> proc_macro2::TokenStream {
254 if let Some(schema) = parsed.container.schema.as_ref() {
255 let value = schema.value();
256 quote! { Some(#value) }
257 } else {
258 quote! { None }
259 }
260}
261
262fn gen_scan_root(fields: &[FieldMeta]) -> proc_macro2::TokenStream {
263 if let Some(field) = fields
264 .iter()
265 .find(|field| is_flatten(field) || is_reference(field))
266 && let Some(ident) = &field.ident
267 {
268 let value = ident.to_string();
269 return quote! { Some(#value) };
270 }
271
272 quote! { None }
273}
274
275fn gen_references(
276 fields: &[FieldMeta],
277 crate_path: &proc_macro2::TokenStream,
278) -> Vec<proc_macro2::TokenStream> {
279 fields
280 .iter()
281 .filter(|field| is_reference(field))
282 .filter_map(|field| {
283 let ident = field.ident.as_ref()?;
284 let logical_name = ident.to_string();
285 let ty = option_inner_type(&field.ty).unwrap_or(&field.ty);
286 if let Some(backref) = field.column.backref.as_ref() {
287 let marker = &backref.path;
288 return Some(quote! {
289 let backref = <#marker as #crate_path::Backref>::meta();
290 refs.push(#crate_path::ReferenceMeta {
291 logical_name: #logical_name,
292 table_name: backref.table_name,
293 table_schema: backref.table_schema,
294 columns: backref.columns,
295 join_type: backref.join_type,
296 });
297 });
298 }
299
300 let reference = field.column.reference.as_ref()?;
301 if let Some(marker) = reference.relation.as_ref() {
302 return Some(quote! {
303 let relation = <#marker as #crate_path::Backref>::meta();
304 refs.push(#crate_path::ReferenceMeta {
305 logical_name: #logical_name,
306 table_name: relation.table_name,
307 table_schema: relation.table_schema,
308 columns: relation.columns,
309 join_type: relation.join_type,
310 });
311 });
312 }
313 let join_type = reference_join_type(reference, &field.ty, crate_path);
314 let columns = reference_columns(reference, crate_path);
315
316 Some(quote! {
317 refs.push(#crate_path::ReferenceMeta {
318 logical_name: #logical_name,
319 table_name: <#ty as #crate_path::Record>::record_table_name(),
320 table_schema: <#ty as #crate_path::Record>::record_table_schema(),
321 columns: &[#(#columns),*],
322 join_type: #join_type,
323 });
324 })
325 })
326 .collect()
327}
328
329fn reference_join_type(
330 reference: &crate::schemable::ReferenceSpec,
331 ty: &Type,
332 crate_path: &proc_macro2::TokenStream,
333) -> proc_macro2::TokenStream {
334 match reference.join.as_deref() {
335 Some("left") => quote! { #crate_path::JoinType::Left },
336 Some("inner") => quote! { #crate_path::JoinType::Inner },
337 _ if is_option(ty) => quote! { #crate_path::JoinType::Left },
338 _ => quote! { #crate_path::JoinType::Inner },
339 }
340}
341
342fn reference_columns(
343 reference: &crate::schemable::ReferenceSpec,
344 crate_path: &proc_macro2::TokenStream,
345) -> Vec<proc_macro2::TokenStream> {
346 if !reference.on.is_empty() {
347 return reference
348 .on
349 .iter()
350 .map(|column| {
351 let from = column.from.as_str();
352 let to = column.to.as_str();
353 quote! { #crate_path::JoinColumn { from: #from, to: #to } }
354 })
355 .collect();
356 }
357 let from = reference.from.as_deref().unwrap_or("");
358 let to = reference.to.as_deref().unwrap_or("id");
359 vec![quote! { #crate_path::JoinColumn { from: #from, to: #to } }]
360}
361
362fn gen_field_initializers_unordered(
364 fields: &[FieldMeta],
365 crate_path: &proc_macro2::TokenStream,
366) -> Vec<proc_macro2::TokenStream> {
367 let mut inits = Vec::with_capacity(fields.len());
368
369 for field in fields {
370 let Some(ident) = &field.ident else {
371 continue;
372 };
373
374 let init = if is_skip(field) || !is_selectable(field) {
375 gen_default_init(ident)
376 } else if is_reference(field) {
377 gen_reference_unordered_error(ident, &field.ty)
379 } else if is_flatten(field) {
380 gen_flatten_init_unordered(ident, &field.ty, crate_path)
381 } else if is_json(field) {
382 gen_json_init_unordered(ident, field, crate_path)
383 } else {
384 gen_scalar_init_unordered(ident, field)
385 };
386
387 inits.push(init);
388 }
389
390 inits
391}
392
393fn gen_default_init(ident: &syn::Ident) -> proc_macro2::TokenStream {
395 quote! {
396 #ident: ::core::default::Default::default(),
397 }
398}
399
400fn gen_reference_unordered_error(ident: &syn::Ident, ty: &Type) -> proc_macro2::TokenStream {
402 let error_msg = format!(
403 "Cannot use record_scan_unordered with reference field '{}' of type '{}'. \
404 Reference fields require ordered scanning (record_scan_ordered) because they use \
405 prefixed column names. Use record_scan_ordered or scan_row instead.",
406 ident,
407 quote::quote!(#ty)
408 );
409 quote! {
410 #ident: {
411 let unsupported: Result<#ty, ::sqlx::Error> =
412 Err(::sqlx::Error::ColumnNotFound(#error_msg.to_string()));
413 unsupported?
414 },
415 }
416}
417
418fn gen_flatten_init(
420 ident: &syn::Ident,
421 ty: &Type,
422 crate_path: &proc_macro2::TokenStream,
423) -> proc_macro2::TokenStream {
424 quote! {
425 #ident: <#ty as #crate_path::Record>::record_scan_ordered(row, start_idx)?,
426 }
427}
428
429fn gen_optional_reference_init(
430 ident: &syn::Ident,
431 ty: &Type,
432 crate_path: &proc_macro2::TokenStream,
433) -> proc_macro2::TokenStream {
434 let inner_ty = option_inner_type(ty).unwrap_or(ty);
435 quote! {
436 #ident: {
437 let nested_cols = <#inner_ty as #crate_path::Record>::record_column_names();
438 let start = *start_idx;
439 let mut all_null = true;
440 for offset in 0..nested_cols.len() {
441 let raw = row.try_get_raw(start + offset)?;
442 if !raw.is_null() {
443 all_null = false;
444 break;
445 }
446 }
447 if all_null {
448 *start_idx += nested_cols.len();
449 None
450 } else {
451 Some(<#inner_ty as #crate_path::Record>::record_scan_ordered(row, start_idx)?)
452 }
453 },
454 }
455}
456
457fn gen_json_init(
459 ident: &syn::Ident,
460 _crate_path: &proc_macro2::TokenStream,
461) -> proc_macro2::TokenStream {
462 quote! {
463 #ident: {
464 let json_val: ::serde_json::Value = row.try_get(*start_idx)?;
465 *start_idx += 1;
466 ::serde_json::from_value(json_val)
467 .map_err(|e| ::sqlx::Error::Decode(Box::new(e)))?
468 },
469 }
470}
471
472fn gen_scalar_init(ident: &syn::Ident) -> proc_macro2::TokenStream {
474 quote! {
475 #ident: {
476 let val = row.try_get(*start_idx)?;
477 *start_idx += 1;
478 val
479 },
480 }
481}
482
483pub(super) fn is_skip(field: &FieldMeta) -> bool {
485 field.column.skip || field.column.prefetch.is_some()
486}
487
488pub(super) fn is_flatten(field: &FieldMeta) -> bool {
490 field.column.flatten
491}
492
493pub(super) fn is_reference(field: &FieldMeta) -> bool {
495 field.column.reference.is_some() || field.column.backref.is_some()
496}
497
498fn is_bindable(field: &FieldMeta) -> bool {
499 !is_skip(field)
500 && !is_reference(field)
501 && !field.column.read_only
502 && !field.column.skip_bind
503 && field.column.insertable.unwrap_or(true)
504 && field.column.updatable.unwrap_or(true)
505}
506
507fn is_option(ty: &Type) -> bool {
508 option_inner_type(ty).is_some()
509}
510
511pub(super) fn option_inner_type(ty: &Type) -> Option<&Type> {
512 let Type::Path(path) = ty else {
513 return None;
514 };
515 if !is_canonical_option(path) {
516 return None;
517 }
518 let segment = path.path.segments.last()?;
519 let PathArguments::AngleBracketed(args) = &segment.arguments else {
520 return None;
521 };
522 let Some(GenericArgument::Type(inner)) = args.args.first() else {
523 return None;
524 };
525 if is_u8_type(inner) {
526 return None;
527 }
528 Some(inner)
529}
530
531fn is_u8_type(ty: &Type) -> bool {
532 let Type::Path(path) = ty else {
533 return false;
534 };
535 if path.qself.is_some() {
536 return false;
537 }
538 let mut segments = path
539 .path
540 .segments
541 .iter()
542 .map(|segment| segment.ident.to_string());
543 matches!(
544 (
545 segments.next(),
546 segments.next(),
547 segments.next(),
548 segments.next()
549 ),
550 (Some(first), None, None, None) if first == "u8"
551 )
552}
553
554pub(super) fn array_inner_type(ty: &Type) -> Option<&Type> {
555 let candidate = option_inner_type(ty).unwrap_or(ty);
556 let Type::Path(path) = candidate else {
557 return None;
558 };
559 if !is_canonical_vec(path) {
560 return None;
561 }
562 let segment = path.path.segments.last()?;
563 let PathArguments::AngleBracketed(args) = &segment.arguments else {
564 return None;
565 };
566 let Some(GenericArgument::Type(inner)) = args.args.first() else {
567 return None;
568 };
569 Some(inner)
570}
571
572fn is_canonical_option(path: &TypePath) -> bool {
573 if path.qself.is_some() {
574 return false;
575 }
576 let mut segments = path
577 .path
578 .segments
579 .iter()
580 .map(|segment| segment.ident.to_string());
581 match (
582 segments.next(),
583 segments.next(),
584 segments.next(),
585 segments.next(),
586 ) {
587 (Some(first), None, None, None) => first == "Option",
588 (Some(first), Some(second), Some(third), None) => {
589 (first == "std" || first == "core") && second == "option" && third == "Option"
590 }
591 _ => false,
592 }
593}
594
595fn is_canonical_vec(path: &TypePath) -> bool {
596 if path.qself.is_some() {
597 return false;
598 }
599 let mut segments = path
600 .path
601 .segments
602 .iter()
603 .map(|segment| segment.ident.to_string());
604 match (
605 segments.next(),
606 segments.next(),
607 segments.next(),
608 segments.next(),
609 ) {
610 (Some(first), None, None, None) => first == "Vec",
611 (Some(first), Some(second), Some(third), None) => {
612 (first == "std" || first == "alloc") && second == "vec" && third == "Vec"
613 }
614 _ => false,
615 }
616}
617
618pub(super) fn is_json(field: &FieldMeta) -> bool {
620 field.column.json
621 || field
622 .column
623 .sql_type
624 .as_ref()
625 .map(|ty| matches!(ty.value().to_ascii_lowercase().as_str(), "json" | "jsonb"))
626 .unwrap_or(false)
627}
628
629pub(super) fn is_selectable(field: &FieldMeta) -> bool {
631 field.column.selectable.unwrap_or(true)
632}
633
634fn gen_record_column_names(
636 fields: &[FieldMeta],
637 crate_path: &proc_macro2::TokenStream,
638) -> Vec<proc_macro2::TokenStream> {
639 let mut stmts = Vec::new();
640
641 for field in fields {
642 if is_skip(field) || !is_selectable(field) {
643 continue;
644 }
645
646 if is_reference(field) {
647 let ty = option_inner_type(&field.ty).unwrap_or(&field.ty);
648 let field_name = field
649 .column
650 .name
651 .as_ref()
652 .map(|lit| lit.value())
653 .or_else(|| field.ident.as_ref().map(|i| i.to_string()))
654 .unwrap_or_default();
655
656 stmts.push(quote! {
657 {
658 let nested_cols = <#ty as #crate_path::Record>::record_column_names();
659 for col in nested_cols {
660 cols.push(format!("{}.{}", #field_name, col));
661 }
662 }
663 });
664 } else if is_flatten(field) {
665 let ty = &field.ty;
666 stmts.push(quote! {
667 cols.extend(<#ty as #crate_path::Record>::record_column_names());
668 });
669 } else {
670 let col_name = field
671 .column
672 .name
673 .as_ref()
674 .map(|lit| lit.value())
675 .or_else(|| field.ident.as_ref().map(|i| i.to_string()))
676 .unwrap_or_default();
677 stmts.push(quote! {
678 cols.push(#col_name.to_string());
679 });
680 }
681 }
682
683 stmts
684}
685
686fn gen_record_bind_column_names(
687 fields: &[FieldMeta],
688 crate_path: &proc_macro2::TokenStream,
689) -> Vec<proc_macro2::TokenStream> {
690 fields
691 .iter()
692 .filter(|field| is_bindable(field))
693 .filter_map(|field| {
694 if is_flatten(field) {
695 let ty = &field.ty;
696 return Some(quote! {
697 cols.extend(<#ty as #crate_path::Record>::record_bind_column_names());
698 });
699 }
700 let col_name = field
701 .column
702 .name
703 .as_ref()
704 .map(|lit| lit.value())
705 .or_else(|| field.ident.as_ref().map(|ident| ident.to_string()))?;
706 Some(quote! {
707 cols.push(#col_name.to_string());
708 })
709 })
710 .collect()
711}
712
713fn gen_bind_statements(
714 fields: &[FieldMeta],
715 crate_path: &proc_macro2::TokenStream,
716) -> Vec<proc_macro2::TokenStream> {
717 fields
718 .iter()
719 .filter(|field| is_bindable(field))
720 .filter_map(|field| {
721 let ident = field.ident.as_ref()?;
722 if is_flatten(field) {
723 let ty = &field.ty;
724 return Some(quote! {
725 <#ty as #crate_path::Record>::record_bind_values(&self.#ident, args)?;
726 });
727 }
728 if is_json(field) {
729 return Some(quote! {
730 {
731 let value = ::serde_json::to_value(&self.#ident)
732 .map_err(|err| ::sqlx::Error::Decode(Box::new(err)))?;
733 ::sqlx::Arguments::add(args, value)
734 .map_err(::sqlx::Error::Decode)?;
735 }
736 });
737 }
738 Some(quote! {
739 {
740 ::sqlx::Arguments::add(args, self.#ident.clone())
741 .map_err(::sqlx::Error::Decode)?;
742 }
743 })
744 })
745 .collect()
746}
747
748fn gen_bind_selected_arms(
749 fields: &[FieldMeta],
750 crate_path: &proc_macro2::TokenStream,
751) -> Vec<proc_macro2::TokenStream> {
752 fields
753 .iter()
754 .filter(|field| is_bindable(field))
755 .filter_map(|field| {
756 let ident = field.ident.as_ref()?;
757 if is_flatten(field) {
758 let ty = &field.ty;
759 return Some(quote! {
760 nested if <#ty as #crate_path::Record>::record_bind_column_names()
761 .iter()
762 .any(|name| name == nested) => {
763 <#ty as #crate_path::Record>::record_bind_selected(
764 &self.#ident,
765 &[nested],
766 args,
767 )?;
768 }
769 });
770 }
771 let col_name = field
772 .column
773 .name
774 .as_ref()
775 .map(|lit| lit.value())
776 .or_else(|| field.ident.as_ref().map(|ident| ident.to_string()))?;
777 if is_json(field) {
778 return Some(quote! {
779 #col_name => {
780 let value = ::serde_json::to_value(&self.#ident)
781 .map_err(|err| ::sqlx::Error::Decode(Box::new(err)))?;
782 ::sqlx::Arguments::add(args, value)
783 .map_err(::sqlx::Error::Decode)?;
784 }
785 });
786 }
787 Some(quote! {
788 #col_name => {
789 ::sqlx::Arguments::add(args, self.#ident.clone())
790 .map_err(::sqlx::Error::Decode)?;
791 }
792 })
793 })
794 .collect()
795}
796
797fn gen_flatten_init_unordered(
799 ident: &syn::Ident,
800 ty: &Type,
801 crate_path: &proc_macro2::TokenStream,
802) -> proc_macro2::TokenStream {
803 quote! {
804 #ident: <#ty as #crate_path::Record>::record_scan_unordered(row)?,
805 }
806}
807
808fn gen_json_init_unordered(
810 ident: &syn::Ident,
811 field: &FieldMeta,
812 _crate_path: &proc_macro2::TokenStream,
813) -> proc_macro2::TokenStream {
814 let col_name = field
815 .column
816 .name
817 .as_ref()
818 .map(|lit| lit.value())
819 .unwrap_or_else(|| ident.to_string());
820
821 quote! {
822 #ident: {
823 let json_val: ::serde_json::Value = row.try_get(#col_name)?;
824 ::serde_json::from_value(json_val)
825 .map_err(|e| ::sqlx::Error::Decode(Box::new(e)))?
826 },
827 }
828}
829
830fn gen_scalar_init_unordered(ident: &syn::Ident, field: &FieldMeta) -> proc_macro2::TokenStream {
832 let col_name = field
833 .column
834 .name
835 .as_ref()
836 .map(|lit| lit.value())
837 .unwrap_or_else(|| ident.to_string());
838
839 quote! {
840 #ident: row.try_get(#col_name)?,
841 }
842}