typed_sql_derive/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use proc_macro2::Span;
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Ident};
6
7#[proc_macro_derive(Table)]
8pub fn table(input: TokenStream) -> TokenStream {
9    let input = parse_macro_input!(input as DeriveInput);
10
11    if let Data::Struct(DataStruct {
12        fields: Fields::Named(fields),
13        ..
14    }) = input.data
15    {
16        let ident = &input.ident;
17        let fields_ident = {
18            let mut s = ident.to_string();
19            s.push_str("Fields");
20            Ident::new(&s, Span::call_site())
21        };
22
23        let struct_fields = fields.named.iter().map(|field| {
24            let name = &field.ident;
25            let ty = &field.ty;
26            quote! {
27                #name: typed_sql::types::Field::<#ident, #ty>,
28            }
29        });
30
31        let default_fields = fields.named.iter().map(|field| {
32            let name = &field.ident;
33            quote! {
34                #name: typed_sql::types::Field::new(stringify!(#name)),
35            }
36        });
37
38        let table_name = {
39            let mut s = ident.to_string().to_lowercase();
40            s.push('s');
41            Ident::new(&s, Span::call_site())
42        };
43
44        let expanded = quote! {
45            struct #fields_ident {
46              #(#struct_fields)*
47            }
48
49            impl Default for #fields_ident {
50                fn default() -> Self {
51                    Self {
52                        #(#default_fields)*
53                    }
54                }
55            }
56
57            impl typed_sql::Table for #ident {
58                const NAME: &'static str = stringify!(#table_name);
59
60                type Fields = #fields_ident;
61            }
62        };
63
64        TokenStream::from(expanded)
65    } else {
66        todo!()
67    }
68}
69
70#[proc_macro_derive(Join)]
71pub fn join(input: TokenStream) -> TokenStream {
72    let input = parse_macro_input!(input as DeriveInput);
73
74    if let Data::Struct(DataStruct {
75        fields: Fields::Named(fields),
76        ..
77    }) = input.data
78    {
79        let ident = input.ident;
80        let fields_ident = format_ident!("{}Fields", ident);
81
82        let struct_fields = fields.named.iter().map(|field| {
83            let name = &field.ident;
84            let ty = &field.ty;
85            quote! {
86                #name: <#ty as typed_sql::Table>::Fields
87            }
88        });
89
90        let mut fields = fields.named.iter();
91        let table = &fields.next().unwrap().ty;
92
93        let join_ident = format_ident!("{}Join", ident);
94        let join_fields = fields.clone().map(|field| {
95            let name = &field.ident;
96            let g = field.ident.as_ref().unwrap().to_string().to_uppercase();
97            let g = format_ident!("{}", g);
98            let ty = &field.ty;
99            quote! {
100                #name: typed_sql::query::select::join::Joined<#g, typed_sql::query::select::join::Inner, #ty>
101            }
102        });
103
104        let generics = fields.map(|field| {
105            Ident::new(
106                &field.ident.as_ref().unwrap().to_string().to_uppercase(),
107                Span::call_site(),
108            )
109        });
110
111        let join_generics = generics.clone().map(|generic| {
112            quote! {
113                #generic
114            }
115        });
116        let join_generics = quote! {
117            #(#join_generics),*
118        };
119
120        let impl_generics = generics.clone().map(|generic| {
121            quote! {
122                #generic: typed_sql::query::Predicate
123            }
124        });
125        let impl_generics = quote! {
126            #(#impl_generics),*
127        };
128
129        let expanded = quote! {
130            #[derive(Default)]
131            struct #fields_ident {
132                #(#struct_fields),*
133            }
134
135            struct #join_ident<#join_generics> {
136                #(#join_fields),*
137            }
138
139            impl<#impl_generics> typed_sql::Join<(#join_generics)> for #ident {
140                type Table = #table;
141                type Fields = #fields_ident;
142                type Join = #join_ident<#join_generics>;
143            }
144
145            impl<#impl_generics> typed_sql::query::select::join::JoinSelect for #join_ident<#join_generics> {
146                type Table = #table;
147                type Fields = #fields_ident;
148
149                fn write_join_select(&self, sql: &mut String) {
150                    self.post.write_join(sql);
151                }
152            }
153        };
154
155        TokenStream::from(expanded)
156    } else {
157        todo!()
158    }
159}
160
161#[proc_macro_derive(Insertable)]
162pub fn insertable(input: TokenStream) -> TokenStream {
163    let input = parse_macro_input!(input as DeriveInput);
164
165    if let Data::Struct(DataStruct {
166        fields: Fields::Named(fields),
167        ..
168    }) = input.data
169    {
170        let ident = &input.ident;
171
172        let write_columns = fields.named.iter().map(|field| {
173            let name = &field.ident;
174            quote! { sql.push_str(stringify!(#name)); }
175        });
176
177        let write_values = fields.named.iter().map(|field| {
178            let name = &field.ident;
179            quote! { self.#name.write_primative(sql); }
180        });
181
182        let expanded = quote! {
183            impl typed_sql::Insertable for #ident {
184                fn write_columns(sql: &mut String) {
185                    #(#write_columns)(sql.push(',');)*
186                }
187
188                fn write_values(&self, sql: &mut String) {
189                    use typed_sql::types::Primitive;
190                    #(#write_values)(sql.push(',');)*
191                }
192            }
193        };
194        TokenStream::from(expanded)
195    } else {
196        todo!()
197    }
198}
199
200#[proc_macro_derive(Binding)]
201pub fn binding(input: TokenStream) -> TokenStream {
202    let input = parse_macro_input!(input as DeriveInput);
203
204    if let Data::Struct(DataStruct {
205        fields: Fields::Named(fields),
206        ..
207    }) = input.data
208    {
209        let ident = &input.ident;
210        let bindings = format_ident!("{}Bindings", ident);
211
212        let bind_fields = fields.named.iter().map(|field| {
213            let name = &field.ident;
214            quote! { #name: typed_sql::types::Bind }
215        });
216
217        let binds = fields.named.iter().map(|field| {
218            let name = &field.ident;
219            quote! { #name: binder.bind() }
220        });
221
222        let values = fields.named.iter().map(|field| {
223            let name = &field.ident;
224            quote! { self.#name.write_primative(sql); }
225        });
226
227        let expanded = quote! {
228            struct #bindings {
229                #(#bind_fields),*
230            }
231
232            impl typed_sql::Binding for #ident {
233                type Bindings = #bindings;
234
235                fn bindings(binder: &mut typed_sql::types::bind::Binder) -> Self::Bindings {
236                    #bindings {
237                        #(#binds),*
238                    }
239                }
240
241                fn write_types(_sql: &mut String) {}
242
243                fn write_values(&self, sql: &mut String) {
244                    use typed_sql::types::Primitive;
245                    #(#values)(sql.push(','))*;
246                }
247            }
248        };
249        TokenStream::from(expanded)
250    } else {
251        todo!()
252    }
253}
254
255#[proc_macro_derive(Queryable)]
256pub fn queryable(input: TokenStream) -> TokenStream {
257    let input = parse_macro_input!(input as DeriveInput);
258
259    if let Data::Struct(DataStruct {
260        fields: Fields::Named(fields),
261        ..
262    }) = input.data
263    {
264        let ident = &input.ident;
265        let columns = fields.named.iter().map(|field| {
266            let name = &field.ident;
267            quote! {
268                sql.push_str(stringify!(#name));
269            }
270        });
271
272        let expanded = quote! {
273            impl typed_sql::Queryable for #ident {
274                fn write_queryable(sql: &mut String) {
275                    #(#columns)(sql.push(','))*
276                }
277            }
278        };
279        TokenStream::from(expanded)
280    } else {
281        todo!()
282    }
283}