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 !reference.is_join_reference() {
302 return None;
303 }
304 if let Some(marker) = reference.relation.as_ref() {
305 return Some(quote! {
306 let relation = <#marker as #crate_path::Backref>::meta();
307 refs.push(#crate_path::ReferenceMeta {
308 logical_name: #logical_name,
309 table_name: relation.table_name,
310 table_schema: relation.table_schema,
311 columns: relation.columns,
312 join_type: relation.join_type,
313 });
314 });
315 }
316 let join_type = reference_join_type(reference, &field.ty, crate_path);
317 let columns = reference_columns(reference, crate_path);
318
319 Some(quote! {
320 refs.push(#crate_path::ReferenceMeta {
321 logical_name: #logical_name,
322 table_name: <#ty as #crate_path::Record>::record_table_name(),
323 table_schema: <#ty as #crate_path::Record>::record_table_schema(),
324 columns: &[#(#columns),*],
325 join_type: #join_type,
326 });
327 })
328 })
329 .collect()
330}
331
332fn reference_join_type(
333 reference: &crate::schemable::ReferenceSpec,
334 ty: &Type,
335 crate_path: &proc_macro2::TokenStream,
336) -> proc_macro2::TokenStream {
337 match reference.join.as_deref() {
338 Some("left") => quote! { #crate_path::JoinType::Left },
339 Some("inner") => quote! { #crate_path::JoinType::Inner },
340 _ if is_option(ty) => quote! { #crate_path::JoinType::Left },
341 _ => quote! { #crate_path::JoinType::Inner },
342 }
343}
344
345fn reference_columns(
346 reference: &crate::schemable::ReferenceSpec,
347 crate_path: &proc_macro2::TokenStream,
348) -> Vec<proc_macro2::TokenStream> {
349 if !reference.on.is_empty() {
350 return reference
351 .on
352 .iter()
353 .map(|column| {
354 let from = column.from.as_str();
355 let to = column.to.as_str();
356 quote! { #crate_path::JoinColumn { from: #from, to: #to } }
357 })
358 .collect();
359 }
360 let from = reference.from.as_deref().unwrap_or("");
361 let to = reference.to.as_deref().unwrap_or("id");
362 vec![quote! { #crate_path::JoinColumn { from: #from, to: #to } }]
363}
364
365fn gen_field_initializers_unordered(
367 fields: &[FieldMeta],
368 crate_path: &proc_macro2::TokenStream,
369) -> Vec<proc_macro2::TokenStream> {
370 let mut inits = Vec::with_capacity(fields.len());
371
372 for field in fields {
373 let Some(ident) = &field.ident else {
374 continue;
375 };
376
377 let init = if is_skip(field) || !is_selectable(field) {
378 gen_default_init(ident)
379 } else if is_reference(field) {
380 gen_reference_unordered_error(ident, &field.ty)
382 } else if is_flatten(field) {
383 gen_flatten_init_unordered(ident, &field.ty, crate_path)
384 } else if is_json(field) {
385 gen_json_init_unordered(ident, field, crate_path)
386 } else {
387 gen_scalar_init_unordered(ident, field)
388 };
389
390 inits.push(init);
391 }
392
393 inits
394}
395
396fn gen_default_init(ident: &syn::Ident) -> proc_macro2::TokenStream {
398 quote! {
399 #ident: ::core::default::Default::default(),
400 }
401}
402
403fn gen_reference_unordered_error(ident: &syn::Ident, ty: &Type) -> proc_macro2::TokenStream {
405 let error_msg = format!(
406 "Cannot use record_scan_unordered with reference field '{}' of type '{}'. \
407 Reference fields require ordered scanning (record_scan_ordered) because they use \
408 prefixed column names. Use record_scan_ordered or scan_row instead.",
409 ident,
410 quote::quote!(#ty)
411 );
412 quote! {
413 #ident: {
414 let unsupported: Result<#ty, ::sqlx::Error> =
415 Err(::sqlx::Error::ColumnNotFound(#error_msg.to_string()));
416 unsupported?
417 },
418 }
419}
420
421fn gen_flatten_init(
423 ident: &syn::Ident,
424 ty: &Type,
425 crate_path: &proc_macro2::TokenStream,
426) -> proc_macro2::TokenStream {
427 quote! {
428 #ident: <#ty as #crate_path::Record>::record_scan_ordered(row, start_idx)?,
429 }
430}
431
432fn gen_optional_reference_init(
433 ident: &syn::Ident,
434 ty: &Type,
435 crate_path: &proc_macro2::TokenStream,
436) -> proc_macro2::TokenStream {
437 let inner_ty = option_inner_type(ty).unwrap_or(ty);
438 quote! {
439 #ident: {
440 let nested_cols = <#inner_ty as #crate_path::Record>::record_column_names();
441 let start = *start_idx;
442 let mut all_null = true;
443 for offset in 0..nested_cols.len() {
444 let raw = row.try_get_raw(start + offset)?;
445 if !raw.is_null() {
446 all_null = false;
447 break;
448 }
449 }
450 if all_null {
451 *start_idx += nested_cols.len();
452 None
453 } else {
454 Some(<#inner_ty as #crate_path::Record>::record_scan_ordered(row, start_idx)?)
455 }
456 },
457 }
458}
459
460fn gen_json_init(
462 ident: &syn::Ident,
463 _crate_path: &proc_macro2::TokenStream,
464) -> proc_macro2::TokenStream {
465 quote! {
466 #ident: {
467 let json_val: ::serde_json::Value = row.try_get(*start_idx)?;
468 *start_idx += 1;
469 ::serde_json::from_value(json_val)
470 .map_err(|e| ::sqlx::Error::Decode(Box::new(e)))?
471 },
472 }
473}
474
475fn gen_scalar_init(ident: &syn::Ident) -> proc_macro2::TokenStream {
477 quote! {
478 #ident: {
479 let val = row.try_get(*start_idx)?;
480 *start_idx += 1;
481 val
482 },
483 }
484}
485
486pub(super) fn is_skip(field: &FieldMeta) -> bool {
488 field.column.skip || field.column.prefetch.is_some()
489}
490
491pub(super) fn is_flatten(field: &FieldMeta) -> bool {
493 field.column.flatten
494}
495
496pub(super) fn is_reference(field: &FieldMeta) -> bool {
498 field
499 .column
500 .reference
501 .as_ref()
502 .is_some_and(|reference| reference.is_join_reference())
503 || field.column.backref.is_some()
504}
505
506fn is_bindable(field: &FieldMeta) -> bool {
507 !is_skip(field)
508 && !is_reference(field)
509 && !field.column.read_only
510 && !field.column.skip_bind
511 && field.column.insertable.unwrap_or(true)
512 && field.column.updatable.unwrap_or(true)
513}
514
515fn is_option(ty: &Type) -> bool {
516 option_inner_type(ty).is_some()
517}
518
519pub(super) fn option_inner_type(ty: &Type) -> Option<&Type> {
520 let Type::Path(path) = ty else {
521 return None;
522 };
523 if !is_canonical_option(path) {
524 return None;
525 }
526 let segment = path.path.segments.last()?;
527 let PathArguments::AngleBracketed(args) = &segment.arguments else {
528 return None;
529 };
530 let Some(GenericArgument::Type(inner)) = args.args.first() else {
531 return None;
532 };
533 if is_u8_type(inner) {
534 return None;
535 }
536 Some(inner)
537}
538
539fn is_u8_type(ty: &Type) -> bool {
540 let Type::Path(path) = ty else {
541 return false;
542 };
543 if path.qself.is_some() {
544 return false;
545 }
546 let mut segments = path
547 .path
548 .segments
549 .iter()
550 .map(|segment| segment.ident.to_string());
551 matches!(
552 (
553 segments.next(),
554 segments.next(),
555 segments.next(),
556 segments.next()
557 ),
558 (Some(first), None, None, None) if first == "u8"
559 )
560}
561
562pub(super) fn array_inner_type(ty: &Type) -> Option<&Type> {
563 let candidate = option_inner_type(ty).unwrap_or(ty);
564 let Type::Path(path) = candidate else {
565 return None;
566 };
567 if !is_canonical_vec(path) {
568 return None;
569 }
570 let segment = path.path.segments.last()?;
571 let PathArguments::AngleBracketed(args) = &segment.arguments else {
572 return None;
573 };
574 let Some(GenericArgument::Type(inner)) = args.args.first() else {
575 return None;
576 };
577 Some(inner)
578}
579
580fn is_canonical_option(path: &TypePath) -> bool {
581 if path.qself.is_some() {
582 return false;
583 }
584 let mut segments = path
585 .path
586 .segments
587 .iter()
588 .map(|segment| segment.ident.to_string());
589 match (
590 segments.next(),
591 segments.next(),
592 segments.next(),
593 segments.next(),
594 ) {
595 (Some(first), None, None, None) => first == "Option",
596 (Some(first), Some(second), Some(third), None) => {
597 (first == "std" || first == "core") && second == "option" && third == "Option"
598 }
599 _ => false,
600 }
601}
602
603fn is_canonical_vec(path: &TypePath) -> bool {
604 if path.qself.is_some() {
605 return false;
606 }
607 let mut segments = path
608 .path
609 .segments
610 .iter()
611 .map(|segment| segment.ident.to_string());
612 match (
613 segments.next(),
614 segments.next(),
615 segments.next(),
616 segments.next(),
617 ) {
618 (Some(first), None, None, None) => first == "Vec",
619 (Some(first), Some(second), Some(third), None) => {
620 (first == "std" || first == "alloc") && second == "vec" && third == "Vec"
621 }
622 _ => false,
623 }
624}
625
626pub(super) fn is_json(field: &FieldMeta) -> bool {
628 field.column.json
629 || field
630 .column
631 .sql_type
632 .as_ref()
633 .map(|ty| matches!(ty.value().to_ascii_lowercase().as_str(), "json" | "jsonb"))
634 .unwrap_or(false)
635}
636
637pub(super) fn is_selectable(field: &FieldMeta) -> bool {
639 field.column.selectable.unwrap_or(true)
640}
641
642fn gen_record_column_names(
644 fields: &[FieldMeta],
645 crate_path: &proc_macro2::TokenStream,
646) -> Vec<proc_macro2::TokenStream> {
647 let mut stmts = Vec::new();
648
649 for field in fields {
650 if is_skip(field) || !is_selectable(field) {
651 continue;
652 }
653
654 if is_reference(field) {
655 let ty = option_inner_type(&field.ty).unwrap_or(&field.ty);
656 let field_name = field
657 .column
658 .name
659 .as_ref()
660 .map(|lit| lit.value())
661 .or_else(|| field.ident.as_ref().map(|i| i.to_string()))
662 .unwrap_or_default();
663
664 stmts.push(quote! {
665 {
666 let nested_cols = <#ty as #crate_path::Record>::record_column_names();
667 for col in nested_cols {
668 cols.push(format!("{}.{}", #field_name, col));
669 }
670 }
671 });
672 } else if is_flatten(field) {
673 let ty = &field.ty;
674 stmts.push(quote! {
675 cols.extend(<#ty as #crate_path::Record>::record_column_names());
676 });
677 } else {
678 let col_name = field
679 .column
680 .name
681 .as_ref()
682 .map(|lit| lit.value())
683 .or_else(|| field.ident.as_ref().map(|i| i.to_string()))
684 .unwrap_or_default();
685 stmts.push(quote! {
686 cols.push(#col_name.to_string());
687 });
688 }
689 }
690
691 stmts
692}
693
694fn gen_record_bind_column_names(
695 fields: &[FieldMeta],
696 crate_path: &proc_macro2::TokenStream,
697) -> Vec<proc_macro2::TokenStream> {
698 fields
699 .iter()
700 .filter(|field| is_bindable(field))
701 .filter_map(|field| {
702 if is_flatten(field) {
703 let ty = &field.ty;
704 return Some(quote! {
705 cols.extend(<#ty as #crate_path::Record>::record_bind_column_names());
706 });
707 }
708 let col_name = field
709 .column
710 .name
711 .as_ref()
712 .map(|lit| lit.value())
713 .or_else(|| field.ident.as_ref().map(|ident| ident.to_string()))?;
714 Some(quote! {
715 cols.push(#col_name.to_string());
716 })
717 })
718 .collect()
719}
720
721fn gen_bind_statements(
722 fields: &[FieldMeta],
723 crate_path: &proc_macro2::TokenStream,
724) -> Vec<proc_macro2::TokenStream> {
725 fields
726 .iter()
727 .filter(|field| is_bindable(field))
728 .filter_map(|field| {
729 let ident = field.ident.as_ref()?;
730 if is_flatten(field) {
731 let ty = &field.ty;
732 return Some(quote! {
733 <#ty as #crate_path::Record>::record_bind_values(&self.#ident, args)?;
734 });
735 }
736 if is_json(field) {
737 return Some(quote! {
738 {
739 let value = ::serde_json::to_value(&self.#ident)
740 .map_err(|err| ::sqlx::Error::Decode(Box::new(err)))?;
741 ::sqlx::Arguments::add(args, value)
742 .map_err(::sqlx::Error::Decode)?;
743 }
744 });
745 }
746 Some(quote! {
747 {
748 ::sqlx::Arguments::add(args, self.#ident.clone())
749 .map_err(::sqlx::Error::Decode)?;
750 }
751 })
752 })
753 .collect()
754}
755
756fn gen_bind_selected_arms(
757 fields: &[FieldMeta],
758 crate_path: &proc_macro2::TokenStream,
759) -> Vec<proc_macro2::TokenStream> {
760 fields
761 .iter()
762 .filter(|field| is_bindable(field))
763 .filter_map(|field| {
764 let ident = field.ident.as_ref()?;
765 if is_flatten(field) {
766 let ty = &field.ty;
767 return Some(quote! {
768 nested if <#ty as #crate_path::Record>::record_bind_column_names()
769 .iter()
770 .any(|name| name == nested) => {
771 <#ty as #crate_path::Record>::record_bind_selected(
772 &self.#ident,
773 &[nested],
774 args,
775 )?;
776 }
777 });
778 }
779 let col_name = field
780 .column
781 .name
782 .as_ref()
783 .map(|lit| lit.value())
784 .or_else(|| field.ident.as_ref().map(|ident| ident.to_string()))?;
785 if is_json(field) {
786 return Some(quote! {
787 #col_name => {
788 let value = ::serde_json::to_value(&self.#ident)
789 .map_err(|err| ::sqlx::Error::Decode(Box::new(err)))?;
790 ::sqlx::Arguments::add(args, value)
791 .map_err(::sqlx::Error::Decode)?;
792 }
793 });
794 }
795 Some(quote! {
796 #col_name => {
797 ::sqlx::Arguments::add(args, self.#ident.clone())
798 .map_err(::sqlx::Error::Decode)?;
799 }
800 })
801 })
802 .collect()
803}
804
805fn gen_flatten_init_unordered(
807 ident: &syn::Ident,
808 ty: &Type,
809 crate_path: &proc_macro2::TokenStream,
810) -> proc_macro2::TokenStream {
811 quote! {
812 #ident: <#ty as #crate_path::Record>::record_scan_unordered(row)?,
813 }
814}
815
816fn gen_json_init_unordered(
818 ident: &syn::Ident,
819 field: &FieldMeta,
820 _crate_path: &proc_macro2::TokenStream,
821) -> proc_macro2::TokenStream {
822 let col_name = field
823 .column
824 .name
825 .as_ref()
826 .map(|lit| lit.value())
827 .unwrap_or_else(|| ident.to_string());
828
829 quote! {
830 #ident: {
831 let json_val: ::serde_json::Value = row.try_get(#col_name)?;
832 ::serde_json::from_value(json_val)
833 .map_err(|e| ::sqlx::Error::Decode(Box::new(e)))?
834 },
835 }
836}
837
838fn gen_scalar_init_unordered(ident: &syn::Ident, field: &FieldMeta) -> proc_macro2::TokenStream {
840 let col_name = field
841 .column
842 .name
843 .as_ref()
844 .map(|lit| lit.value())
845 .unwrap_or_else(|| ident.to_string());
846
847 quote! {
848 #ident: row.try_get(#col_name)?,
849 }
850}