use std::{
hash::{Hash, Hasher},
sync::atomic::Ordering,
};
use crate::{
attribute::{CustomMeta, MetaLocation},
keyword,
row::{Row, RowField},
unique_macro::unique_macro,
};
use super::{column::Column, relation::Relation};
use convert_case::{Case, Casing};
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, format_ident, quote};
use syn::{
Attribute, Ident, Token,
parse::{Parse, ParseStream},
punctuated::Punctuated,
};
pub struct Table {
_token_stream: TokenStream,
pub _inner_attrs: Vec<Attribute>,
pub _outer_attrs: Vec<Attribute>,
pub _create: keyword::create,
pub _table: keyword::table,
pub name: Ident,
pub _paren: syn::token::Paren,
pub columns: Punctuated<Column, Token![,]>,
pub _semi: Token![;],
pub relations: Punctuated<Relation, Token![,]>,
}
impl Parse for Table {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
_token_stream: input.fork().parse()?,
_inner_attrs: {
let attrs = Attribute::parse_inner(input)?;
CustomMeta::parse_attrs(&attrs, MetaLocation::TableInner)?;
attrs
},
_outer_attrs: {
let attrs = Attribute::parse_outer(input)?;
CustomMeta::parse_attrs(&attrs, MetaLocation::TableOuter)?;
attrs
},
_create: input.call(keyword::create::parse_autocomplete)?,
_table: input.call(keyword::table::parse_autocomplete)?,
name: input.parse()?,
_paren: syn::parenthesized!(content in input),
columns: content.parse_terminated(Column::parse, Token![,])?,
_semi: input.parse()?,
relations: input.parse_terminated(Relation::parse, Token![,])?,
})
}
}
impl ToTokens for Table {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.name.to_string();
let rust_name = Ident::new(
&self.name.to_string().to_case(Case::Snake),
self.name.span(),
);
let columns = self.columns.iter();
let relations = self.relations.iter();
let column_names = self
.columns
.iter()
.map(Column::rust_name)
.collect::<Vec<_>>();
let relation_names = self
.relations
.iter()
.map(|relation| &relation.name)
.collect::<Vec<_>>();
let select_struct = Row::new(
vec![],
Ident::new("Select", Span::call_site()),
self.columns
.iter()
.map(|column| {
let column = column.rust_name();
RowField::new(vec![], column.clone(), quote! { columns::#column::Type })
})
.collect(),
);
let star_macro = {
let unique_macro_name = unique_macro!("__kosame_star_{}", self.name.span());
let fields = self.columns.iter().map(|column| {
let column_name = column.rust_name();
RowField::new(
vec![],
column_name.clone(),
quote! { $($table_path)* ::columns::#column_name::Type },
)
});
quote! {
#[macro_export]
macro_rules! #unique_macro_name {
(
($($table_path:tt)*)
$(#[$meta:meta])* pub struct $name:ident { $($tokens:tt)* }
) => {
$(#[$meta])*
pub struct $name {
#(#fields,)*
$($tokens)*
}
}
}
pub use #unique_macro_name as star;
}
};
let inject_macro = {
let unique_macro_name = unique_macro!("__kosame_inject_{}", self.name.span());
let token_stream = &self._token_stream;
quote! {
#[macro_export]
macro_rules! #unique_macro_name {
(
$(#![$acc:meta])*
($($child:tt)*) {
$($content:tt)*
}
($($table_path:tt)*)
) => {
$($child)* {
#![kosame(__table($($table_path)* = #token_stream))]
$(#![$acc])*
$($content)*
}
}
}
pub use #unique_macro_name as inject;
}
};
quote! {
pub mod #rust_name {
pub mod columns {
#(#columns)*
}
pub mod relations {
#(#relations)*
}
pub mod columns_and_relations {
pub use super::columns::*;
pub use super::relations::*;
}
pub const TABLE_NAME: &str = #name;
pub const TABLE: ::kosame::repr::schema::Table<'_> = ::kosame::repr::schema::Table::new(
#name,
&[#(&columns::#column_names::COLUMN),*],
&[#(&relations::#relation_names::RELATION),*],
);
#select_struct
#star_macro
#inject_macro
}
}
.to_tokens(tokens);
}
}