db_proc_macro 0.1.1

Procedural macros for db_core project
Documentation
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericArgument, PathArguments, Type};
use crate::create_crate_ident;
// 把一个 struct 转成 Option<struct>, 所有字段都变成 Option
pub fn derive_to_option(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let struct_name = input.ident.clone();
    let option_name = format_ident!("{}Option", struct_name);
    let crate_ident = create_crate_ident("db_cores");

    let root = if crate_ident == "crate" {
        quote!(crate)
    } else {
        quote!(::#crate_ident)
    };


    // 获取原 struct 的字段
    let fields: Vec<&syn::Field> = if let Data::Struct(ds) = &input.data {
        if let Fields::Named(named) = &ds.fields {
            named.named.iter().collect()
        } else {
            panic!("DeriveOption only supports named fields")
        }
    } else {
        panic!("DeriveOption only supports structs")
    };

    // 生成 Option 结构体字段,直接生成 quote! 片段
    let option_fields = fields.iter().map(|f| {
        let ident = &f.ident;
        let ty = &f.ty;

        // 判断字段是否已经是 Option<T>
        let is_option = match ty {
            | Type::Path(tp) => tp.path.segments.first().map(|seg| seg.ident == "Option").unwrap_or(false),

            | _ => false,
        };
        let opt_ty = if is_option {
            quote! { #ty } // 已经是 Option
        } else {
            quote! { Option<#ty> } // 普通类型包 Option
        };
        quote! { pub #ident: #opt_ty } // 直接生成 quote! TokenStream
    });

    // 为每个字段生成 hashmap 插入逻辑
    let update_inserts: Vec<_> = fields
        .iter()
        .map(|f| {
            let key = f.ident.as_ref().unwrap().to_string();
            let ident = f.ident.as_ref().unwrap();
            quote! {
                if let Some(v) = t.#ident {
                    let sql_value: #root::SqlValue= v.into();
                    map.insert(#key.to_string(), sql_value);
                }
            }
        })
        .collect();

    // 检测 #[primary_key]
    let primary_key_field = fields
        .iter()
        .find(|f| f.attrs.iter().any(|attr| attr.path().is_ident("primary_key")))
        .and_then(|f| f.ident.as_ref().map(|i| i.to_string()));
    let primary_key_name = primary_key_field.unwrap_or_else(|| "id".to_string());

    // === 生成 insert_map 的字段插入逻辑(针对原结构体) ===
    let insert_inserts = fields.iter().map(|f| {
        let key = f.ident.as_ref().unwrap().to_string();
        let ident = f.ident.as_ref().unwrap();
        if key == primary_key_name {
            quote! {
                let id_value = #root::utlis::nanoid2(None).into();
                map.insert(#key.to_string(), id_value);
            }
        } else {
            quote! {
                let sql_value: #root::SqlValue = t.#ident.into();
                map.insert(#key.to_string(), sql_value);
            }
        }
    });

    let match_key = fields.iter().map(|f| {
        let ident = f.ident.as_ref().unwrap();
        let field_name_str = ident.to_string();
        let key_str = syn::LitStr::new(&field_name_str, ident.span());
        let inner_type = get_inner_type(&f.ty);
        quote! {
            #key_str => {
                let v: #inner_type = #root::serde_json::from_value(value.clone())?;
                let sql_value: #root::SqlValue = v.into();
                Ok(sql_value)
            }
        }
    });
    let expanded = quote! {
        #[derive(Debug, #root::serde::Serialize, #root::serde::Deserialize, Clone, Default)]
        pub struct #option_name {
            #(#option_fields,)*
        }

        impl #struct_name {
            pub fn to_sqlvalue(key_str:&str,value:&#root::serde_json::Value)->::anyhow::Result<#root::SqlValue> {
                match key_str {
                    #(#match_key,)*
                    _ =>{
                        Err(::anyhow::anyhow!("ERROR to_sqlvalue 没有该字段 {}",key_str))
                    }
                }
            }
            pub fn insert_map(value: &serde_json::Value)->anyhow::Result<std::collections::HashMap<String, #root::SqlValue>> {
                let t = #root::serde_json::from_value::<#struct_name>(value.clone())?;
                let mut map = std::collections::HashMap::new();
                #(#insert_inserts)*
                Ok(map)
            }
            // rust中  #option_name 是原struct 的属性全变成 option
            pub fn update_map(value: &mut #root::serde_json::Value)->anyhow::Result<std::collections::HashMap<String, #root::SqlValue>> {
                let mut map = std::collections::HashMap::new();
                // 2️⃣ 遍历原始 JSON,判断哪些字段显式传了 null,假如显示为null,则表示要update为NULL
                let mut null_fields:Vec<String> = Vec::new();
                if let #root::serde_json::Value::Object(obj) = value {
                    obj.retain(|key, v| {
                        let is_null_like = v.is_null() || (v.is_string() && v.as_str() == Some("NULL"));
                        if is_null_like {
                            null_fields.push(key.clone());
                        }
                        // 保留非 null 的项
                        !is_null_like
                    });
                }
                // 先用 #option_name struct解析一次,这样可以起到type处理,与数据校验的作用;
                let t = #root::serde_json::from_value::<#option_name>(value.clone())?;
                #(#update_inserts)*
                for i in null_fields {
                    map.insert(i, #root::SqlValue::Null);
                }
                Ok(map)
            }
            // 用于对前端转过来的 where条件 ,处理成 方便sql绑定的类型
            pub fn where_map(value: &#root::serde_json::Value) -> ::anyhow::Result<Vec<#root::BuildConditionItem>> {
                // use ::structs::{BuildConditionItem, Logical, Operator, WhereValue};
                use #root::serde_json::Value;
                #[derive(Debug, #root::serde::Serialize, #root::serde::Deserialize, PartialEq, Clone)]
                struct ConditionItem {
                    column: String,
                    values: Value,
                    operator: #root::Operator,
                    logical: #root::Logical,
                }
                let conditions: Vec<ConditionItem> = serde_json::from_value(value.clone())
                    .map_err(|e| ::anyhow::anyhow!("where_map: 解析 JSON 失败: {e}"))?;
                let mut result = Vec::with_capacity(conditions.len());
                for i in conditions {
                   let field_name = i.column;
                   let where_value =  if let Value::Array(arr) = i.values {
                        let mut values = vec![];
                        for v in arr {
                            let sql_value = Self::to_sqlvalue(&field_name, &v)?;
                            values.push(sql_value);
                        }
                        #root::WhereValue::List(values)
                    } else {
                        let sql_value = Self::to_sqlvalue(&field_name, &i.values)?;
                        #root::WhereValue::Value(sql_value)
                    };
                    let item = #root::BuildConditionItem {
                        column: field_name,
                        values: where_value,
                        operator: i.operator,
                        logical: i.logical
                    };
                    result.push(item);
                }
                Ok(result)
            }

        }
    };
    expanded.into()
}

fn get_inner_type(ty: &Type) -> &Type {
    // 判断是否是 Option<T>
    if let Type::Path(type_path) = ty {
        if let Some(seg) = type_path.path.segments.last() {
            if seg.ident == "Option" {
                if let PathArguments::AngleBracketed(angle_bracketed) = &seg.arguments {
                    if let Some(GenericArgument::Type(inner_ty)) = angle_bracketed.args.first() {
                        return inner_ty;
                    }
                }
            }
        }
    }
    ty
}