mod cols;
mod column_trait;
mod decode_column;
mod decode_expression;
mod decode_join;
mod decode_table;
mod encode_column_def;
mod encode_column_ref;
mod frag_evaluated;
mod from_row_trait;
use crate::{
cols::ColList,
decode_column::ColumnMetadata,
decode_table::{TableMetadata, decode_table},
encode_column_def::encode_column_def,
from_row_trait::from_row_trait,
};
use column_trait::column_trait;
use decode_column::decode_column;
use decode_expression::decode_expression;
use decode_join::JoinParsed;
use frag_evaluated::flag_evaluated;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
Expr, Ident, Index, ItemStruct, parse_macro_input, parse2, punctuated::Punctuated,
token::AndAnd,
};
#[proc_macro_derive(Entity, attributes(tank))]
pub fn derive_entity(input: TokenStream) -> TokenStream {
let table = decode_table(parse_macro_input!(input as ItemStruct));
let ident = &table.item.ident;
let name = &table.name;
let schema = &table.schema;
let metadata_and_filter = table
.columns
.iter()
.map(|metadata| {
let filter_passive = if let Some(ref filter_passive) = metadata.check_passive {
let field = &metadata.ident;
filter_passive(quote!(self.#field))
} else {
quote!(true)
};
(metadata, filter_passive)
})
.collect::<Vec<_>>();
let (from_row_factory, from_row) = from_row_trait(&table);
let primary_key_cols = table.primary_key.iter().map(|i| &table.columns[*i]);
let primary_key = primary_key_cols.clone().map(|col| {
let ident = &col.ident;
quote!(self.#ident)
});
let primary_keys_def = table.primary_key.iter().map(|i| quote!(&columns[#i]));
let unique_defs = &table
.unique
.iter()
.map(|v| {
if v.is_empty() {
quote!()
} else {
let i = v.iter();
quote!(vec![#(&columns[#i]),*].into_boxed_slice())
}
})
.collect::<Vec<_>>();
let unique_defs = quote!(vec![#(#unique_defs),*].into_boxed_slice());
let primary_key_types = primary_key_cols.clone().map(|col| col.ty.clone());
let (column_trait, column) = column_trait(&table);
let label_value_and_filter = metadata_and_filter.iter().map(|(column, filter)| {
let name = &column.name;
let field = &column.ident;
quote!((#name.into(), ::tank::AsValue::as_value(self.#field.clone()), #filter))
});
let row_full = metadata_and_filter.iter().map(
|(ColumnMetadata { ident, .. }, _)| quote!(::tank::AsValue::as_value(self.#ident.clone())),
);
let columns = metadata_and_filter.iter().map(|(c, _)| {
let field = &c.ident;
encode_column_def(&c, quote!(<#ident as #column_trait>::#field))
});
let primary_key_condition = table.primary_key.iter().enumerate().map(|(i, pki)| {
let ident = table.columns[*pki].ident.clone();
let span = ident.span();
(ident, Ident::new(&format!("pk{}", i), span))
});
let primary_key_condition_declaration = primary_key_condition
.clone()
.enumerate()
.clone()
.map(|(i, (_, pk))| {
let i = Index::from(i);
quote! { let #pk = primary_key.#i.to_owned(); }
})
.collect::<TokenStream2>();
let primary_key_condition_expression = primary_key_condition
.clone()
.map(|(field, pk)| quote!(#ident::#field == # #pk))
.collect::<Punctuated<_, AndAnd>>();
quote! {
#from_row
#column
impl ::tank::Entity for #ident {
type PrimaryKey<'a> = (#(&'a #primary_key_types,)*);
fn table() -> &'static ::tank::TableRef {
static RESULT: ::std::sync::LazyLock<Box<::tank::TableRef>> =
::std::sync::LazyLock::new(||
Box::new(
::tank::TableRef {
name: ::std::borrow::Cow::Borrowed(#name),
schema: ::std::borrow::Cow::Borrowed(#schema),
alias: ::std::borrow::Cow::Borrowed(""),
columns: #ident::columns(),
primary_key: #ident::primary_key_def(),
}
)
);
RESULT.as_ref()
}
fn columns() -> &'static [::tank::ColumnDef] {
static RESULT: ::std::sync::LazyLock<Box<[::tank::ColumnDef]>> =
::std::sync::LazyLock::new(|| vec![#(#columns),*].into_boxed_slice());
&RESULT
}
fn primary_key_def() -> &'static [&'static ::tank::ColumnDef] {
static RESULT: ::std::sync::LazyLock<Box<[&'static ::tank::ColumnDef]>> =
::std::sync::LazyLock::new(|| {
let columns = <#ident as ::tank::Entity>::columns();
vec![#(#primary_keys_def),*].into_boxed_slice()
});
&RESULT
}
fn primary_key<'a>(&'a self) -> Self::PrimaryKey<'a> {
(#(&#primary_key,)*)
}
fn primary_key_expr(&self) -> impl ::tank::Expression {
let primary_key = self.primary_key();
#primary_key_condition_declaration
::tank::expr!(#primary_key_condition_expression)
}
fn unique_defs()
-> impl ExactSizeIterator<Item = impl ExactSizeIterator<Item = &'static ::tank::ColumnDef>> {
static RESULT: ::std::sync::LazyLock<Box<[Box<[&'static ::tank::ColumnDef]>]>> =
::std::sync::LazyLock::new(|| {
let columns = #ident::columns();
#unique_defs
});
RESULT.iter().map(|v| v.iter().copied())
}
fn row_filtered(&self) -> Box<[(&'static str, ::tank::Value)]> {
[#(#label_value_and_filter),*]
.into_iter()
.filter_map(|(n, v, f)| if f { Some((n, v)) } else { None })
.collect()
}
fn row_full(&self) -> ::tank::RowValues {
[#(#row_full),*].into()
}
fn from_row(row: ::tank::Row) -> ::tank::Result<Self> {
#from_row_factory::<Self>::from_row(row)
}
}
}
.into()
}
#[proc_macro]
pub fn join(input: TokenStream) -> TokenStream {
let result = parse_macro_input!(input as JoinParsed);
result.0.into()
}
#[proc_macro]
pub fn expr(input: TokenStream) -> TokenStream {
let mut input: TokenStream = flag_evaluated(input.into()).into();
if input.is_empty() {
input = quote!(false).into();
}
let expr = parse_macro_input!(input as Expr);
let parsed = decode_expression(&expr);
quote!(#parsed).into()
}
#[proc_macro]
pub fn cols(input: TokenStream) -> TokenStream {
let input = flag_evaluated(input.into());
let Ok(ColList { cols: items }) = parse2(input) else {
panic!("Could not parse the columns");
};
let generated = items.iter().map(|item| {
let expr = &item.expr;
match &item.order {
Some(order) => {
quote! {
::tank::Ordered {
order: #order,
expression: ::tank::expr!(#expr),
}
}
}
None => {
quote! { ::tank::expr!(#expr) }
}
}
});
TokenStream::from(quote! {
&[ #( &#generated as &dyn ::tank::Expression ),* ]
})
}