kosame_macro 0.0.1

Macro-based Rust ORM focused on developer ergonomics
Documentation
mod bind_params;
mod field;
mod filter;
mod limit;
mod node;
mod node_path;
mod offset;
mod order_by;
mod star;

use field::QueryField;
use filter::Filter;
use limit::Limit;
use node::QueryNode;
use node_path::QueryNodePath;
use offset::Offset;
use order_by::OrderBy;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::{
    Attribute, Ident,
    parse::{Parse, ParseStream},
};

use crate::{alias::Alias, path_ext::PathExt, query::bind_params::BindParamsBuilder};

pub struct Query {
    attrs: Vec<Attribute>,
    table: syn::Path,
    body: QueryNode,
    alias: Option<Alias>,
}

impl Parse for Query {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            attrs: input.call(Attribute::parse_outer)?,
            table: input.parse()?,
            body: input.parse()?,
            alias: input.call(Alias::parse_optional)?,
        })
    }
}

impl ToTokens for Query {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let module_name = match &self.alias {
            Some(alias) => alias.ident(),
            None => &Ident::new("internal", Span::call_site()),
        };

        let bind_params = {
            let mut builder = BindParamsBuilder::new();
            self.body.accept_expr(&mut builder);
            builder.build()
        };
        let closure_tokens = self
            .alias
            .is_none()
            .then(|| bind_params.to_closure_token_stream(module_name));

        let node_tokens = {
            let mut tokens = proc_macro2::TokenStream::new();
            self.body
                .to_row_struct_tokens(&mut tokens, self, &QueryNodePath::new());
            tokens
        };

        let query_node = {
            let mut tokens = TokenStream::new();
            self.body
                .to_query_node_tokens(&mut tokens, self, QueryNodePath::new());
            tokens
        };

        let lifetime = (!bind_params.is_empty()).then_some(quote! { <'a> });

        let module_tokens = quote! {
            pub mod #module_name {
                #node_tokens

                #bind_params

                pub struct Query #lifetime {
                    params: Params #lifetime,
                }

                impl #lifetime Query #lifetime {
                    pub fn new(params: Params #lifetime) -> Self { Self { params } }
                }

                impl #lifetime ::kosame::query::Query for Query #lifetime {
                    type Params = Params #lifetime;
                    type Row = Row;

                    const ROOT: ::kosame::query::QueryNode = #query_node;

                    fn params(&self) -> &Self::Params {
                        &self.params
                    }

                    fn execute<'c, C>(
                        &self,
                        connection: &mut C,
                        runner: &mut (impl ::kosame::query::Runner + ?Sized),
                    ) -> impl Future<Output = Result<Vec<<Self as ::kosame::query::Query>::Row>, C::Error>>
                    where
                        C: ::kosame::Connection,
                        <Self as ::kosame::query::Query>::Params: ::kosame::params::Params<C::Params<'c>>,
                        for<'b> <Self as ::kosame::query::Query>::Row: From<&'b C::Row>,
                    {
                        runner.execute(connection, self)
                    }
                }
            }
        };

        if self.alias.is_some() {
            module_tokens.to_tokens(tokens);
        } else {
            quote! {
                {
                    #closure_tokens

                    #module_tokens

                    #module_name::Query::new(closure)
                }
            }
            .to_tokens(tokens);
        }
    }
}