1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{Path, Token};

use sqlx::describe::Describe;

use crate::database::DatabaseExt;

pub struct RustColumn {
    pub(super) ident: Ident,
    pub(super) type_: TokenStream,
}

pub fn columns_to_rust<DB: DatabaseExt>(describe: &Describe<DB>) -> crate::Result<Vec<RustColumn>> {
    describe
        .result_columns
        .iter()
        .enumerate()
        .map(|(i, column)| -> crate::Result<_> {
            let name = column
                .name
                .as_deref()
                .ok_or_else(|| format!("column at position {} must have a name", i))?;

            let ident = syn::parse_str::<Ident>(name)
                .map_err(|_| format!("{:?} is not a valid Rust identifier", name))?;

            let type_ = <DB as DatabaseExt>::return_type_for_id(&column.type_id)
                .ok_or_else(|| format!("unknown field type ID: {}", &column.type_id))?
                .parse::<TokenStream>()
                .unwrap();

            Ok(RustColumn { ident, type_ })
        })
        .collect::<crate::Result<Vec<_>>>()
}

pub fn quote_query_as<DB: DatabaseExt>(
    sql: &str,
    out_ty: &Path,
    columns: &[RustColumn],
) -> TokenStream {
    let instantiations = columns.iter().enumerate().map(
        |(
            i,
            &RustColumn {
                ref ident,
                ref type_,
                ..
            },
        )| { quote!( #ident: #i.try_get::<#type_>(&row).try_unwrap_optional()? ) },
    );

    let db_path = DB::quotable_path();

    quote! {
        sqlx::query_as_mapped::<#db_path, _>(#sql, |row| {
            use sqlx::row::RowIndex as _;
            use sqlx::result_ext::ResultExt as _;
            Ok(#out_ty { #(#instantiations),* })
        })
    }
}