db-derive-impl 0.1.8

Derive proc-macro for db-derive
Documentation
use {
    crate::parser::{self, QueryType},
    proc_macro2::TokenStream,
    syn::{Generics, Ident},
};

pub fn new_impl(
    ident: &Ident,
    generics: Generics,
    sql: Option<String>,
    postgres: Option<String>,
    sqlite: Option<String>,
) -> TokenStream {
    if sql.is_none() && postgres.is_none() {
        panic!("you must provide an sql or postgres query")
    }

    if sql.is_none() && sqlite.is_none() {
        panic!("you must provide an sql or sqlite query")
    }

    let (postgres_sql, postgres_params) = if cfg!(feature = "postgresql") {
        let p = sql_params(QueryType::PostgreSQL, postgres, sql.clone());

        (Some(p.0), Some(p.1))
    } else {
        (None, None)
    };

    let (sqlite_sql, sqlite_params) = if cfg!(feature = "sqlite") {
        let p = sql_params(QueryType::SQLite, sqlite, sql);

        (Some(p.0), Some(p.1))
    } else {
        (None, None)
    };

    if cfg!(all(feature = "postgresql", feature = "sqlite"))
        && postgres_params.clone().unwrap().len() != sqlite_params.clone().unwrap().len()
    {
        panic!("the queries for postgres and sqlite do not have a matching amount of parameters");
    }

    let param_count = if cfg!(feature = "postgresql") {
        postgres_params.clone().unwrap().len()
    } else if cfg!(feature = "sqlite") {
        sqlite_params.clone().unwrap().len()
    } else {
        0
    };

    let mut new_generics = generics.clone();

    new_generics.params.push(syn::parse_quote!('__query));

    let (_, type_generics, _) = generics.split_for_impl();
    let (impl_generics, _, where_clause) = new_generics.split_for_impl();

    let postgresql = if cfg!(feature = "postgresql") {
        let sql = postgres_sql.unwrap();
        let params = postgres_params.unwrap();

        Some(quote::quote! {
            fn sql_postgres(&'__query self) -> Self::Sql {
                #sql
            }

            type ParamsPostgres = [&'__query (dyn ::db_derive::internal::PostgresToSQL + Sync); #param_count];

            fn params_postgres(&'__query self) -> Self::ParamsPostgres {
                [ #( &self.#params, )* ]
            }
        })
    } else {
        None
    };

    let sqlite: Option<TokenStream> = if cfg!(feature = "sqlite") {
        let sql = sqlite_sql.unwrap();
        let params = sqlite_params.unwrap();

        Some(quote::quote! {
            fn sql_sqlite(&'__query self) -> Self::Sql {
                #sql
            }

            type ParamsSQLite = [&'__query dyn ::db_derive::internal::SQLiteToSQL; #param_count];

            fn params_sqlite(&'__query self) -> Self::ParamsSQLite {
                [ #( &self.#params, )* ]
            }
        })
    } else {
        None
    };

    quote::quote! {
        impl #impl_generics ::db_derive::prelude::Sql<'__query> for #ident #type_generics #where_clause {
            type Sql = &'__query str;

            #postgresql

            #sqlite
        }
    }
}

pub fn sql_params(
    typ: QueryType,
    specific: Option<String>,
    fallback: Option<String>,
) -> (String, Vec<Ident>) {
    let query = if let Some(sql) = specific {
        sql
    } else {
        fallback.expect("you must provide sql, postgres, or sqlite queries")
    };

    let (sql, params) = parser::parse_query(typ, query);

    let mut sorted_params = params.into_iter().collect::<Vec<_>>();

    sorted_params.sort_by_key(|(_, index)| *index);

    (
        sql,
        sorted_params
            .into_iter()
            .map(|(ident, _)| ident)
            .collect::<Vec<_>>(),
    )
}