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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
mod parse;

use darling::FromDeriveInput;
use proc_macro::{self, TokenStream};
use quote::quote;
use syn::parse_macro_input;

use parse::ModelInput;

/// Automatically implement `Model` for your struct.
///
/// ## Attributes
///  * `table` - for structs:
///      - `table_name: String`: optional. Overwrites the table name
///  * `column` - for struct fields:
///      - `dtype: String`: optional. Overwrites the postgres datatype
///      - `unique: bool`: optional, default: `false`. Enables the `unqiue` constraint.
///      - `auto: bool`: optional, default: `false`. This autogenerated the
///        values.
///      - `column_name: String`: optional. Overwrites the column name.
#[proc_macro_derive(Model, attributes(table, column))]
pub fn derive(input: TokenStream) -> TokenStream {
    let opts = ModelInput::from_derive_input(&parse_macro_input!(input)).unwrap();

    let ident = opts.ident();

    let table_name = opts.table_name();

    // Retrieve the struct's fields
    let fields = opts.fields().collect::<Vec<_>>();

    if opts.n_fields() == 0 {
        panic!("struct must have at least one field to become a `Model`")
    }

    let n_fields = opts.n_fields();

    // Get the fields' idents
    let field_idents = fields
        .clone()
        .into_iter()
        .map(|f| f.ident())
        .collect::<Vec<_>>();
    let column_names = fields.iter().map(|f| f.column_name()).collect::<Vec<_>>();

    let impl_insert = opts.impl_insert();

    let table_creation_sql = opts.table_creation_sql();

    let impl_column_consts = opts.impl_column_consts();

    // Generate the needed impl code
    let output = quote!(
        #impl_column_consts

        impl<'a> TryFrom<&'a pg_worm::Row> for #ident {
            type Error = pg_worm::Error;

            fn try_from(value: &'a pg_worm::Row) -> Result<#ident, Self::Error> {
                // Parse each column into the corresponding field
                Ok(#ident {
                    #(#field_idents: value
                        .try_get(#column_names)?
                    ),*
                })
            }
        }

        #[pg_worm::async_trait]
        impl Model<#ident> for #ident {
            #[inline]
            fn _table_creation_sql() -> &'static str {
                #table_creation_sql
            }

            fn columns() -> &'static [&'static pg_worm::DynCol] {
                &#ident::COLUMNS
            }

            async fn select(filter: pg_worm::Filter) -> Vec<#ident> {
                use pg_worm::QueryBuilder;

                let query = pg_worm::Query::select(#ident::COLUMNS)
                    .filter(filter)
                    .build();

                let res: Vec<#ident> = query
                    .exec()
                    .await
                    .expect("failed to query");

                res
            }

            async fn select_one(filter: pg_worm::Filter) -> Option<#ident> {
                use pg_worm::QueryBuilder;

                let query = pg_worm::Query::select(#ident::COLUMNS)
                    .filter(filter)
                    .build();

                let res: Vec<#ident> = query
                    .exec()
                    .await
                    .expect("failed to query");

                res.into_iter().next()
            }

            async fn delete(filter: pg_worm::Filter) -> u64 {
                // Retrieve client. Panic if not connected
                let client = pg_worm::_get_client()
                    .expect("not connected to db");

                // Convert args to correct datatype
                let args: Vec<&(dyn pg_worm::pg::types::ToSql + Sync)> = filter
                    ._args()
                    .into_iter()
                    .map(|i| &**i as _)
                    .collect();

                // Make the query
                let rows_affected = client
                    .execute(
                        // Fill in table name and filter
                        format!(
                            "DELETE FROM {} {}",
                            #table_name,
                            if filter._stmt().is_empty() { "".to_string() }
                            else { format!("WHERE {}", filter._stmt()) }
                        ).as_str(),
                        // Pass filter arguments
                        args.as_slice()
                    ).await.unwrap();

                // Return the number of rows affected
                rows_affected
            }
        }

        impl #ident {
            #impl_insert

            const COLUMNS: [&'static pg_worm::DynCol; #n_fields] = [
                #(
                    &#ident::#field_idents
                ),*
            ];
        }
    );

    output.into()
}