use std::any::Any;
#[allow(unused_imports)]
use proc_macro2::{Ident, Span, TokenStream};
use quote::{ToTokens, quote};
use syn::{
Data, DataStruct, DeriveInput, Fields, FieldsNamed, GenericArgument, Type, TypePath,
parse_macro_input, spanned::Spanned,
};
use crate::{derive::TableDerive, internal::TableState};
pub fn generate_table_builder(
ident: &syn::Ident,
generics: &syn::Generics,
table: &TableDerive,
) -> Result<TokenStream, syn::Error> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let table_name = quote::format_ident!("{}", table.name);
Ok(quote! {
impl #impl_generics geekorm::prelude::TableBuilder for #ident #ty_generics #where_clause {
fn table() -> geekorm::Table {
#table
}
fn get_table(&self) -> geekorm::Table {
#ident::table()
}
fn table_name() -> String {
stringify!(#table_name).to_string()
}
}
})
}
pub fn generate_query_builder(
ident: &syn::Ident,
generics: &syn::Generics,
table: &TableDerive,
) -> Result<TokenStream, syn::Error> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut insert_values = TokenStream::new();
for column in table.columns.columns.iter() {
if column.skip {
continue;
}
let name = &column.name;
let ident = syn::Ident::new(name.as_str(), name.span());
insert_values.extend(quote! {
.add_value(#name, &item.#ident)
});
}
Ok(quote! {
impl #impl_generics geekorm::prelude::QueryBuilderTrait for #ident #ty_generics #where_clause {
fn query_create() -> geekorm::QueryBuilder {
geekorm::QueryBuilder::create()
.table(#ident::table())
}
fn query_select() -> geekorm::QueryBuilder {
geekorm::QueryBuilder::select()
.table(#ident::table())
}
fn query_insert(item: &Self) -> geekorm::Query {
geekorm::QueryBuilder::insert()
.table(#ident::table())
#insert_values
.build()
.expect("Failed to build insert query")
}
fn query_update(item: &Self) -> geekorm::Query {
geekorm::QueryBuilder::update()
.table(#ident::table())
#insert_values
.build()
.expect("Failed to build update query")
}
fn query_delete(item: &Self) -> geekorm::Query {
geekorm::QueryBuilder::delete()
.table(#ident::table())
.where_eq(#ident::primary_key().as_str(), item.primary_key_value())
.build()
.expect("Failed to build delete query")
}
fn query_count() -> geekorm::QueryBuilder {
geekorm::QueryBuilder::select()
.table(#ident::table())
.count()
}
}
})
}
pub fn generate_table_primary_key(
ident: &syn::Ident,
generics: &syn::Generics,
table: &TableDerive,
) -> Result<TokenStream, syn::Error> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
if let Some(key) = table.columns.get_primary_key() {
let name = key.name.clone();
let identifier = syn::Ident::new(name.as_str(), name.span());
Ok(quote! {
impl #impl_generics geekorm::prelude::TablePrimaryKey for #ident #ty_generics #where_clause {
fn primary_key() -> String {
String::from(#name)
}
fn primary_key_value(&self) -> geekorm::Value {
geekorm::Value::from(&self.#identifier)
}
}
})
} else {
Err(syn::Error::new(
ident.span(),
"Table must have a primary key",
))
}
}
#[allow(dead_code)]
pub fn generate_backend(
ident: &syn::Ident,
fields: &FieldsNamed,
_generics: &syn::Generics,
table: &TableDerive,
) -> Result<TokenStream, syn::Error> {
let mut stream = TokenStream::new();
let mut insert_values = TokenStream::new();
let mut fetch_impl = TokenStream::new();
let mut fetch_functions = TokenStream::new();
let mut auto_update = TokenStream::new();
let mut where_previous = false;
let mut where_clauses = TokenStream::new();
let mut unique_where = TokenStream::new();
for column in table.columns.columns.iter() {
if column.skip {
continue;
}
let name = &column.name;
let ident = syn::Ident::new(name.as_str(), name.span());
insert_values.extend(quote! {
self.#ident = item.#ident.clone();
});
if let Some(update) = &column.update {
let auto = syn::parse_str::<TokenStream>(update).map_err(|err| {
syn::Error::new(
column.span(),
format!("Failed to parse data for New mode: {}", err),
)
})?;
auto_update.extend(quote! {
self.#ident = #auto;
});
}
if column.is_searchable() {
if where_previous {
where_clauses.extend(quote! {
.or()
});
}
where_clauses.extend(quote! {
.where_like(stringify!(#ident), format!("%{}%", search))
});
where_previous = true;
}
if column.is_unique() {
unique_where.extend(quote! {
.where_eq(stringify!(#ident), &self.#ident)
});
}
if column.is_foreign_key() == true {
let field = fields
.named
.iter()
.find(|f| f.ident.as_ref().unwrap() == &column.name)
.unwrap();
let field_type = match &field.ty {
syn::Type::Path(path) => path.path.segments.first().unwrap(),
_ => {
return Err(syn::Error::new(
field.ty.span(),
"Only path types are supported for foreign keys",
));
}
};
let inner_type = match &field_type.arguments {
syn::PathArguments::AngleBracketed(args) => args.args.last().unwrap(),
_ => {
return Err(syn::Error::new(
field.ty.span(),
"Only angle bracketed arguments are supported for foreign keys",
));
}
};
match inner_type {
syn::GenericArgument::Type(Type::Path(path)) => {
let fident = path.path.segments.first().unwrap().ident.clone();
fetch_impl.extend(column.get_fetcher(&ident, &fident));
let func_name = format!("fetch_{}", column.identifier);
let func = Ident::new(&func_name, Span::call_site());
fetch_functions.extend(quote! {
Self::#func(self, connection).await?;
});
}
_ => {
return Err(syn::Error::new(
field.ty.span(),
"Only type arguments are supported for foreign keys",
));
}
}
}
let func = syn::Ident::new(format!("fetch_by_{}", name).as_str(), name.span());
let select_func =
syn::Ident::new(format!("query_select_by_{}", name).as_str(), name.span());
if column.is_unique() {
fetch_impl.extend(quote! {
pub async fn #func<'a, C>(
connection: &'a C,
value: impl Into<geekorm::Value>
) -> Result<Self, geekorm::Error>
where
C: geekorm::GeekConnection<Connection = C> + 'a,
Self: geekorm::QueryBuilderTrait + serde::Serialize + serde::de::DeserializeOwned
{
C::query_first::<Self>(
connection,
Self:: #select_func(value.into())
).await
}
});
} else {
fetch_impl.extend(quote! {
pub async fn #func<'a, C>(
connection: &'a C,
value: impl Into<geekorm::Value>
) -> Result<Vec<Self>, geekorm::Error>
where
C: geekorm::GeekConnection<Connection = C> + 'a,
Self: geekorm::QueryBuilderTrait + serde::Serialize + serde::de::DeserializeOwned
{
C::query::<Self>(
connection,
Self:: #select_func(value.into())
).await
}
});
}
}
stream.extend(quote! {
#[automatically_derived]
impl<'a, T> geekorm::GeekConnector<'a, T> for #ident where
T: geekorm::GeekConnection<Connection = T> + 'a,
Self: geekorm::QueryBuilderTrait + serde::Serialize + serde::de::DeserializeOwned
{
#[allow(async_fn_in_trait, unused_variables)]
async fn save(&mut self, connection: &'a T) -> Result<(), geekorm::Error>
{
T::execute(connection, Self::query_insert(self)).await?;
let select_query = #ident::query_select()
.order_by(#ident::primary_key().as_str(), geekorm::QueryOrder::Desc)
.limit(1)
.build()?;
let item: #ident = T::query_first::<Self>(connection, select_query).await?;
#insert_values
Ok(())
}
#[allow(async_fn_in_trait, unused_variables)]
async fn update(&mut self, connection: &'a T) -> Result<(), geekorm::Error> {
#auto_update
T::execute(connection, Self::query_update(self)).await
}
#[allow(async_fn_in_trait, unused_variables)]
async fn fetch(&mut self, connection: &'a T) -> Result<(), geekorm::Error>
{
#fetch_functions
Ok(())
}
#[allow(async_fn_in_trait, unused_variables)]
async fn fetch_or_create(
&mut self,
connection: &'a T,
) -> Result<(), geekorm::Error>
{
let query = Self::query_select()
#unique_where
.build()?;
match T::query_first::<Self>(connection, query).await {
Ok(item) => {
*self = item;
},
Err(_) => {
self.save(connection).await?;
}
}
Ok(())
}
#[allow(async_fn_in_trait, unused_variables)]
async fn search(
connection: &'a T,
search: impl Into<String>,
) -> Result<Vec<Self>, geekorm::Error>
{
let search = search.into();
Ok(T::query::<Self>(
connection,
geekorm::QueryBuilder::select()
.table(Self::table())
#where_clauses
.build()?
).await?)
}
}
});
if let Some(key) = table.columns.get_primary_key() {
fetch_impl.extend(key.get_fetcher_pk(ident));
}
stream.extend(quote! {
#[automatically_derived]
impl #ident
{
#fetch_impl
}
});
Ok(stream)
}