#![feature(proc_macro_span)]
mod sql_fragment;
mod sql_expand;
use proc_macro::TokenStream;
use sql_expand::SqlExpand;
use sql_fragment::{STATIC_SQL_FRAGMENT_MAP, SqlFragment};
use syn::{parse_macro_input, Token};
use std::{collections::HashMap, sync::RwLock, path::PathBuf};
use quote::quote;
use sql_fragment::get_sql_fragment;
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct DyClosure {
executor_info: ExecutorInfo,
dto_info: DtoInfo,
sql_name: Option<String>,
ret_type: Option<syn::Path>, body: String,
source_file: PathBuf,
}
#[derive(Debug)]
enum RefKind {
Immutable,
Mutable,
None
}
#[derive(Debug)]
struct DtoInfo {
src: Option<syn::Ident>,
ref_kind: RefKind,
}
impl DtoInfo {
pub fn new(src: Option<syn::Ident>, ref_kind: RefKind) -> Self {
Self {
src,
ref_kind,
}
}
pub fn gen_token(&self) -> proc_macro2::TokenStream {
if let Some(_) = self.src {
let mut rst = match self.ref_kind {
RefKind::Immutable => quote!(&),
RefKind::Mutable => quote!(&mut),
RefKind::None => quote!(),
};
let dto = &self.src;
rst.extend(quote!(#dto));
rst.into()
} else {
quote!()
}
}
}
#[derive(Debug)]
struct ExecutorInfo {
src: syn::Ident,
ref_kind: RefKind,
is_deref: bool,
}
impl ExecutorInfo {
pub fn new(src: syn::Ident, ref_kind: RefKind, is_deref: bool) -> Self {
Self {
src,
ref_kind,
is_deref,
}
}
pub fn gen_token(&self) -> proc_macro2::TokenStream {
let mut rst = match self.ref_kind {
RefKind::Immutable => quote!(&),
RefKind::Mutable => quote!(&mut),
RefKind::None => quote!(),
};
if self.is_deref {
rst.extend(quote!(*))
}
let executor = &self.src;
rst.extend(quote!(#executor));
quote!((#rst))
}
}
impl syn::parse::Parse for DyClosure {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
dotenv::dotenv().ok();
input.parse::<syn::Token!(|)>()?;
let executor_ref_kind: RefKind;
match input.parse::<syn::Token!(&)>() {
Err(_) => executor_ref_kind = RefKind::None,
Ok(_) => match input.parse::<syn::Token!(mut)>() {
Err(_) => executor_ref_kind = RefKind::Immutable,
Ok(_) => executor_ref_kind = RefKind::Mutable,
}
}
let is_executor_deref: bool;
let executor: syn::Ident;
match input.parse::<syn::Token!(*)>() {
Ok(_) => is_executor_deref = true,
Err(_) => is_executor_deref = false,
}
match input.parse::<syn::Ident>() {
Err(e) => return Err(e),
Ok(ex) => executor = ex,
}
let sql_name: Option<String>;
let dto: Option<syn::Ident>;
let dto_ref_kind: RefKind;
match input.parse::<syn::Token!(|)>() {
Ok(_) => {
sql_name = None;
dto = None;
dto_ref_kind = RefKind::None;
},
Err(_) => match input.parse::<syn::Token!(,)>() {
Err(e) => return Err(e),
Ok(_) => {
match input.parse::<syn::Token!(_)>() {
Ok(_) => {
dto = None;
dto_ref_kind = RefKind::None;
},
Err(_) => {
match input.parse::<syn::Token!(&)>() {
Ok(_) => match input.parse::<syn::Token!(mut)>(){
Ok(_) => dto_ref_kind = RefKind::Mutable,
Err(_) => dto_ref_kind = RefKind::Immutable,
},
Err(_) => dto_ref_kind = RefKind::None,
}
match input.parse::<syn::Ident>() {
Err(e) => return Err(e),
Ok(d) => dto = Some(d),
}
}
}
match input.parse::<syn::Token!(|)>() {
Ok(_) => sql_name = None,
Err(_) => match input.parse::<syn::Token!(,)>() {
Err(e) => return Err(e),
Ok(_) => {
match input.parse::<syn::Token!(_)>() {
Ok(_) => { sql_name = None },
Err(_) => match input.parse::<syn::LitStr>() {
Ok(s) => sql_name = Some(s.value()),
Err(_) => return Err(syn::Error::new(proc_macro2::Span::call_site(), "need specify the sql_name")),
}
}
input.parse::<syn::Token!(|)>()?;
}
}
}
}
}
}
let ret_type:Option<syn::Path>;
match input.parse::<syn::Token!(->)>() {
Ok(_) => match input.parse::<syn::Path>() {
Ok(p) => ret_type = Some(p),
Err(_) =>
return Err(syn::Error::new(proc_macro2::Span::call_site(), "Need specify the return type")),
}
Err(_) => ret_type = None,
};
let body = parse_body(input)?;
let body: Vec<String> = body.split('\n').into_iter().map(|f| f.trim().to_owned()).collect();
let body = body.join(" ").to_owned();
let span: proc_macro::Span = input.span().unwrap();
let source_file = span.source_file().path();
let executor_info = ExecutorInfo::new(executor, executor_ref_kind, is_executor_deref);
let dto_info = DtoInfo::new(dto, dto_ref_kind);
let dsf = DyClosure { executor_info, dto_info, sql_name, ret_type, body, source_file };
Ok(dsf)
}
}
fn parse_body(input: &syn::parse::ParseBuffer) -> Result<String, syn::Error> {
let body_buf;
syn::braced!(body_buf in input);
let ts = body_buf.cursor().token_stream().into_iter();
let mut sql = String::new();
for it in ts {
match it {
proc_macro2::TokenTree::Group(_) => {
return Err(syn::Error::new(input.span(), "error not support group in sql".to_owned()));
},
proc_macro2::TokenTree::Ident(_) => {
let v: syn::Ident = body_buf.parse()?;
let sql_fragment = get_sql_fragment(&v.to_string());
if let Some(s) = sql_fragment {
sql.push_str(&s);
} else {
return Err(syn::Error::new(input.span(), "error not found sql identity".to_owned()));
}
},
proc_macro2::TokenTree::Punct(v) => {
if v.to_string() == "+" {
body_buf.parse::<Token!(+)>()?;
} else {
return Err(syn::Error::new(input.span(), "error only support '+' expr".to_owned()));
}
},
proc_macro2::TokenTree::Literal(_) => {
let rst: syn::LitStr = body_buf.parse()?;
sql.push_str(&rst.value());
},
};
}
Ok(sql)
}
#[proc_macro]
pub fn fetch_all(input: TokenStream) -> TokenStream {
let st = syn::parse_macro_input!(input as DyClosure);
if st.ret_type.is_none() { panic!("return type can't be null.") }
match SqlExpand.fetch_all(&st) {
Ok(ret) => ret.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[proc_macro]
pub fn fetch_one(input: TokenStream) -> TokenStream {
let st = syn::parse_macro_input!(input as DyClosure);
if st.ret_type.is_none() { panic!("return type can't be null.") }
match SqlExpand.fetch_one(&st) {
Ok(ret) => ret.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[proc_macro]
pub fn fetch_scalar(input: TokenStream) -> TokenStream {
let st = syn::parse_macro_input!(input as DyClosure);
if st.ret_type.is_none() { panic!("return type can't be null.") }
match SqlExpand.fetch_scalar(&st) {
Ok(ret) => ret.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[proc_macro]
pub fn execute(input: TokenStream) -> TokenStream {
let st = syn::parse_macro_input!(input as DyClosure);
match SqlExpand.execute(&st) {
Ok(ret) => ret.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[proc_macro]
pub fn insert(input: TokenStream) -> TokenStream {
let st = syn::parse_macro_input!(input as DyClosure);
if st.ret_type.is_none() { panic!("return type can't be null.") }
match SqlExpand.insert(&st) {
Ok(ret) => ret.into(),
Err(e) => e.into_compile_error().into(),
}
}
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let st = parse_macro_input!(input as SqlFragment);
let cache = STATIC_SQL_FRAGMENT_MAP.get_or_init(|| {
RwLock::new(HashMap::new())
});
cache.write().unwrap().insert(st.name, st.value.to_string());
quote!().into()
}
#[proc_macro]
pub fn page(input: TokenStream) -> TokenStream {
let st = syn::parse_macro_input!(input as DyClosure);
if st.ret_type.is_none() { panic!("return type can't be null.") }
match SqlExpand.page(&st) {
Ok(ret) => ret.into(),
Err(e) => e.into_compile_error().into(),
}
}