typed-sql-derive 0.1.2

Macro implementation for typed-sql
Documentation
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Ident};

#[proc_macro_derive(Table)]
pub fn table(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    if let Data::Struct(DataStruct {
        fields: Fields::Named(fields),
        ..
    }) = input.data
    {
        let ident = &input.ident;
        let fields_ident = {
            let mut s = ident.to_string();
            s.push_str("Fields");
            Ident::new(&s, Span::call_site())
        };

        let struct_fields = fields.named.iter().map(|field| {
            let name = &field.ident;
            let ty = &field.ty;
            quote! {
                #name: typed_sql::types::Field::<#ident, #ty>,
            }
        });

        let default_fields = fields.named.iter().map(|field| {
            let name = &field.ident;
            quote! {
                #name: typed_sql::types::Field::new(stringify!(#name)),
            }
        });

        let table_name = {
            let mut s = ident.to_string().to_lowercase();
            s.push('s');
            Ident::new(&s, Span::call_site())
        };

        let expanded = quote! {
            struct #fields_ident {
              #(#struct_fields)*
            }

            impl Default for #fields_ident {
                fn default() -> Self {
                    Self {
                        #(#default_fields)*
                    }
                }
            }

            impl typed_sql::Table for #ident {
                const NAME: &'static str = stringify!(#table_name);

                type Fields = #fields_ident;
            }
        };

        TokenStream::from(expanded)
    } else {
        todo!()
    }
}

#[proc_macro_derive(Join)]
pub fn join(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    if let Data::Struct(DataStruct {
        fields: Fields::Named(fields),
        ..
    }) = input.data
    {
        let ident = input.ident;
        let fields_ident = format_ident!("{}Fields", ident);

        let struct_fields = fields.named.iter().map(|field| {
            let name = &field.ident;
            let ty = &field.ty;
            quote! {
                #name: <#ty as typed_sql::Table>::Fields
            }
        });

        let mut fields = fields.named.iter();
        let table = &fields.next().unwrap().ty;

        let join_ident = format_ident!("{}Join", ident);
        let join_fields = fields.clone().map(|field| {
            let name = &field.ident;
            let g = field.ident.as_ref().unwrap().to_string().to_uppercase();
            let g = format_ident!("{}", g);
            let ty = &field.ty;
            quote! {
                #name: typed_sql::query::select::join::Joined<#g, typed_sql::query::select::join::Inner, #ty>
            }
        });

        let generics = fields.map(|field| {
            Ident::new(
                &field.ident.as_ref().unwrap().to_string().to_uppercase(),
                Span::call_site(),
            )
        });

        let join_generics = generics.clone().map(|generic| {
            quote! {
                #generic
            }
        });
        let join_generics = quote! {
            #(#join_generics),*
        };

        let impl_generics = generics.clone().map(|generic| {
            quote! {
                #generic: typed_sql::query::Predicate
            }
        });
        let impl_generics = quote! {
            #(#impl_generics),*
        };

        let expanded = quote! {
            #[derive(Default)]
            struct #fields_ident {
                #(#struct_fields),*
            }

            struct #join_ident<#join_generics> {
                #(#join_fields),*
            }

            impl<#impl_generics> typed_sql::Join<(#join_generics)> for #ident {
                type Table = #table;
                type Fields = #fields_ident;
                type Join = #join_ident<#join_generics>;
            }

            impl<#impl_generics> typed_sql::query::select::join::JoinSelect for #join_ident<#join_generics> {
                type Table = #table;
                type Fields = #fields_ident;

                fn write_join_select(&self, sql: &mut String) {
                    self.post.write_join(sql);
                }
            }
        };

        TokenStream::from(expanded)
    } else {
        todo!()
    }
}

#[proc_macro_derive(Insertable)]
pub fn insertable(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    if let Data::Struct(DataStruct {
        fields: Fields::Named(fields),
        ..
    }) = input.data
    {
        let ident = &input.ident;

        let write_columns = fields.named.iter().map(|field| {
            let name = &field.ident;
            quote! { sql.push_str(stringify!(#name)); }
        });

        let write_values = fields.named.iter().map(|field| {
            let name = &field.ident;
            quote! { self.#name.write_primative(sql); }
        });

        let expanded = quote! {
            impl typed_sql::Insertable for #ident {
                fn write_columns(sql: &mut String) {
                    #(#write_columns)(sql.push(',');)*
                }

                fn write_values(&self, sql: &mut String) {
                    use typed_sql::types::Primitive;
                    #(#write_values)(sql.push(',');)*
                }
            }
        };
        TokenStream::from(expanded)
    } else {
        todo!()
    }
}

#[proc_macro_derive(Binding)]
pub fn binding(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    if let Data::Struct(DataStruct {
        fields: Fields::Named(fields),
        ..
    }) = input.data
    {
        let ident = &input.ident;
        let bindings = format_ident!("{}Bindings", ident);

        let bind_fields = fields.named.iter().map(|field| {
            let name = &field.ident;
            quote! { #name: typed_sql::types::Bind }
        });

        let binds = fields.named.iter().map(|field| {
            let name = &field.ident;
            quote! { #name: binder.bind() }
        });

        let values = fields.named.iter().map(|field| {
            let name = &field.ident;
            quote! { self.#name.write_primative(sql); }
        });

        let expanded = quote! {
            struct #bindings {
                #(#bind_fields),*
            }

            impl typed_sql::Binding for #ident {
                type Bindings = #bindings;

                fn bindings(binder: &mut typed_sql::types::bind::Binder) -> Self::Bindings {
                    #bindings {
                        #(#binds),*
                    }
                }

                fn write_types(_sql: &mut String) {}

                fn write_values(&self, sql: &mut String) {
                    use typed_sql::types::Primitive;
                    #(#values)(sql.push(','))*;
                }
            }
        };
        TokenStream::from(expanded)
    } else {
        todo!()
    }
}

#[proc_macro_derive(Queryable)]
pub fn queryable(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    if let Data::Struct(DataStruct {
        fields: Fields::Named(fields),
        ..
    }) = input.data
    {
        let ident = &input.ident;
        let columns = fields.named.iter().map(|field| {
            let name = &field.ident;
            quote! {
                sql.push_str(stringify!(#name));
            }
        });

        let expanded = quote! {
            impl typed_sql::Queryable for #ident {
                fn write_queryable(sql: &mut String) {
                    #(#columns)(sql.push(','))*
                }
            }
        };
        TokenStream::from(expanded)
    } else {
        todo!()
    }
}