sqlx-model-core 0.0.1-beta.1

sqlx model core

#[macro_export]
macro_rules! model_option_var {
    ($struct_name:ident,{$($key:ident:$val:expr),*$(,)?})=>{
        {
            $struct_name{
                $(
                    $key:Some(&$val),
                )*
                ..$struct_name::none_default()
            }
        }
    };
}



#[macro_export]
macro_rules! model_option_field {
    ($struct_name:ident,$var:expr,{$($key:ident),*$(,)?})=>{
        {
            $struct_name{
                $(
                    $key:Some(&$var.$key),
                )*
                ..$struct_name::none_default()
            }
        }
    };
    ($struct_name:ident,$var:expr,{$($from_key:tt=>$to_key:ident),*$(,)?})=>{
        {
            $struct_name{
                $(
                    $to_key:Some(&$var.$from_key),
                )*
                ..$struct_name::none_default()
            }
        }
    };
}


#[test]
fn test_model_option_field(){
    #[derive(Clone,Debug)]
    #[allow(dead_code)]
    struct UserModel {
        id: u32,
        nickname: String,
        gender: u8,
        headimg: String,
        password_id: u32,
    }
    #[derive(PartialEq,Debug)]
    struct UserModelOption<'t>{
        id:  Option<&'t u32>,
        nickname: Option<&'t String>,
        gender: Option<&'t u8>,
        headimg: Option<&'t String>,
        password_id:Option<&'t u32>
    }
    impl<'t>UserModelOption<'t> {
        pub fn none_default()->Self{
            UserModelOption {
                nickname:None,
                gender:None,
                id:  None,
                headimg: None,
                password_id:None
            }
        }
    }
    let tvar1=(
        "option insert".to_string(),
        1
    );
    let tmp=crate::model_option_field!(UserModelOption,tvar1,{0=>nickname,1=>gender});
    assert_eq!(tmp.nickname.unwrap(),&tvar1.0);
    assert_eq!(tmp.gender.unwrap(),&tvar1.1);
    
    struct TVAR{a:String,b:u8}
    let tvar1=TVAR{
        a:"option insert".to_string(),
        b:1
    };
    let tmp=crate::model_option_field!(UserModelOption,tvar1,{a=>nickname,b=>gender});
    assert_eq!(tmp,UserModelOption{
        nickname:Some(&tvar1.a),
        gender:Some(&tvar1.b),
        id:  None,
        headimg: None,
        password_id:None
    });
    let nike_name="option insert".to_string();
    let gender=1;
    let userinsert=crate::model_option_var!(UserModelOption,{
        nickname:nike_name,
        gender:gender,
    });
    assert_eq!(userinsert,UserModelOption{
        nickname:Some(&tvar1.a),
        gender:Some(&tvar1.b),
        id:  None,
        headimg: None,
        password_id:None
    });
}

#[macro_export]
macro_rules! model_table_option_define {
    ($self_var:ident,$struct_name:ident,$option_struct_name:ident,{$($name:ident:$type:ty),+})=>{
        #[derive(PartialEq,Debug)]
        pub struct $option_struct_name<'t> {
            $($name:Option<&'t $type>),*
        }
        impl<'t> $option_struct_name<'t> {
            #[allow(dead_code)]
            pub fn none_default()->Self{
                $option_struct_name {
                    $($name:None),*
                }
            }
        }
        impl<'t> $crate::InsertData<'t,sqlx::MySql> for $option_struct_name<'t> 
        {
            fn columns(&$self_var) -> Vec<$crate::FieldItem> {
                let mut vec = vec![];
                $(
                    if !$self_var.$name.is_none() {
                        vec.push($crate::FieldItem::new(stringify!($name)));
                    }
                ) *
                vec
            }
            fn sqlx_bind<'q>(&'q  
                $self_var,
                field:&$crate::FieldItem,
                mut res: sqlx::query::Query<'q,sqlx::MySql,<sqlx::MySql as sqlx::database::HasArguments<'q>>::Arguments>,
            ) -> sqlx::query::Query<'q,sqlx::MySql,<sqlx::MySql as sqlx::database::HasArguments<'q>>::Arguments>{
                $crate::model_table_value_bind_define!(value_bind $self_var, res, field, {$($name),+});
            }
        }
        impl<'t> $crate::ModelInsertData<'t,sqlx::MySql,$option_struct_name<'t>> for $struct_name 
        {
            fn insert_data(&'t $self_var) -> $option_struct_name<'t>{
                $option_struct_name {
                    $(
                       $name:Some(&$self_var.$name)
                    ),*
                }
            }
        }
        impl<'t> $crate::UpdateData<'t,sqlx::MySql> for $option_struct_name<'t> 
        {
            fn diff_columns(&$self_var) -> Vec<$crate::FieldItem> {
                let mut vec = vec![];
                $(
                    if !$self_var.$name.is_none() {
                        vec.push($crate::FieldItem::new(stringify!($name)));
                    }
                ) *
                vec
            }
            fn sqlx_bind<'q>(&'q  
                $self_var,
                mut res: sqlx::query::Query<'q,sqlx::MySql,<sqlx::MySql as sqlx::database::HasArguments<'q>>::Arguments>,
            ) -> sqlx::query::Query<'q,sqlx::MySql,<sqlx::MySql as sqlx::database::HasArguments<'q>>::Arguments>
            {
                $(
                    if let Some(val) = $self_var.$name {
                        res = res.bind(val.clone());
                    }
                ) *
                res
            }
        }
        impl<'t> $crate::ModelUpdateData<'t,sqlx::MySql, $option_struct_name<'t>> for $struct_name 
        {
            fn diff(&'t $self_var, source_opt: Option<&Self>) -> $option_struct_name<'t> {

                match source_opt {
                    Some(source) => {
                        $option_struct_name {$(
                            $name: if $self_var.$name != source.$name {
                                Some(&$self_var.$name)
                            } else {
                                None
                            }
                        ),*}
                    }
                    None => $option_struct_name {
                        $(
                           $name:Some(&$self_var.$name)
                        ),*
                    },
                }
            }
        }
    };
    //实现 ModelTableName ModelTableField 
    // @param $struct_name 结构体名
    // @param $option_struct_name 更改值临时存储的结构体名
    // @param {$name:$type} 字段名列表:类型列表
    ($struct_name:ident,$option_struct_name:ident,{$($name:ident:$type:ty),+$(,)?})=>{
        $crate::model_table_option_define!(self,$struct_name,$option_struct_name,{$($name:$type),+});
    };
}

#[macro_export]
macro_rules! model_table_value_bind_define {
    (value_bind $self_var:ident,$res:expr,$val:expr,{$($name:ident),+})=>{
            match $val.name.as_str() {
                $(
                    stringify!($name)=> {
                        $res=$res.bind(&$self_var.$name);
                    }
                ) *
                _=>{}
            }
            return $res
    };
    ($self_var:ident,$struct_name:ident,$table_name:expr,{$($name:ident),+},{$($pk_name:ident),+})=>{
        impl $crate::ModelTableName for $struct_name {
            fn table_name() -> $crate::TableName {
                $crate::TableName::new($table_name)
            }
        }
        impl $crate::ModelTableField<sqlx::MySql> for $struct_name{
            fn table_pk() -> $crate::TableFields {
                $crate::TableFields::new(vec![
                    $(
                        $crate::FieldItem::new(stringify!($pk_name))
                        
                    ),*
                ])
            }
            fn table_column() -> $crate::TableFields {
                $crate::TableFields::new(vec![
                    $(
                        $crate::FieldItem::new(stringify!($name))
                    ),*
                ])
            }
            fn query_sqlx_bind<'t>(
                &'t 
                $self_var,
                field_val: &$crate::FieldItem,
                mut res: sqlx::query::Query<'t,sqlx::MySql,<sqlx::MySql as sqlx::database::HasArguments<'t>>::Arguments>, 
            ) -> sqlx::query::Query<'t,sqlx::MySql,<sqlx::MySql as sqlx::database::HasArguments<'t>>::Arguments>
            {
                $crate::model_table_value_bind_define!(value_bind $self_var, res, field_val, {$($name),+});
            }
            fn query_as_sqlx_bind<'t, M>(
                &'t $self_var,
                field_val: &$crate::FieldItem,
                mut res:  sqlx::query::QueryAs<'t,sqlx::MySql, M,<sqlx::MySql as sqlx::database::HasArguments<'t>>::Arguments>,
            ) -> sqlx::query::QueryAs<'t,sqlx::MySql, M,<sqlx::MySql as sqlx::database::HasArguments<'t>>::Arguments>
            where
                for<'r> M: sqlx::FromRow<'r, sqlx::mysql::MySqlRow> + Send + Unpin,
            {
                $crate::model_table_value_bind_define!(value_bind $self_var, res, field_val,{$($name),+});
            }
        }
    };
    //实现 ModelTableName ModelTableField 
    // @param $struct_name 结构体名
    // @param $table_name 表名
    // @param {$name} 字段名列表
    // @param {$pk_name} 主键字段名列表
    ($struct_name:ident,$table_name:expr,{$($name:ident),+},{$($pk_name:ident),+$(,)?})=>{
        $crate::model_table_value_bind_define!(self ,$struct_name,$table_name,{$($name),+},{$($pk_name),+});
    };
}

#[macro_export]
//根据标记生成绑定SQL语句.和绑定资源
macro_rules! model_sql_bind {
    ($db:ty,$sql:expr,[$($key:literal),+$(,)?])=>{
        {
            let mut query_sql=$sql.to_string();
            let mut posv:std::collections::HashMap<usize,(String,usize)>=std::collections::HashMap::new();
            $(
                for (pos,mstr) in query_sql.match_indices($key) {
                    let len=$key.len();
                    if let Some((_,vallen))=posv.get(&pos) {
                        if len>*vallen {
                            posv.insert(pos, (mstr.to_string(),len));
                        }
                    } else {
                        posv.insert(pos, (mstr.to_string(),len));
                    }
                }
            )+
            let mut spos = posv.into_iter().collect::<Vec<_>>();
            spos.sort_by(|a,b|if a.1.1<b.1.1 {
              std::cmp::Ordering::Greater
            }else {
              std::cmp::Ordering::Less
            });
            for(pos,val)in spos.iter().enumerate(){
                let bsts=$crate::DbType::type_new::<$db>().mark(pos);
                query_sql = query_sql.replace(val.1.0.as_str(),bsts.as_str());
            }
            spos.sort_by_key(|a|a.0);
            let mut match_vec = vec![];
            for (_,(key,_)) in spos.into_iter(){
                match_vec.push(key.clone());
            };
            (query_sql,match_vec)
        }
    };
}
#[macro_export]
//绑定资源迭代绑定
macro_rules! model_sql_bind_match {
    ($bind_res:expr,{$($key:literal:$bind:expr),+$(,)?})=>{
        {
            for key in $bind_res.iter() {
                match key.as_str(){
                    $(
                        $key=>{$bind},
                    )+
                    _=>{},
                }
            }
        }
    };
}

#[tokio::test]
async fn test_build_db(){
    use crate::SqlQuote;
    let db=crate::test::test_db().await;
    #[derive(sqlx::FromRow,Clone,Debug,Default)]
    struct UserModel {
        nickname: String,
        gender: u8,
    }
    let nikename="ddd";
    let gender=1;
    let gender_group=vec![1,2];
    let (sql,bind_res)=model_sql_bind!(
        sqlx::MySql,
        r#"
            select * from (SELECT :nickname as nickname,:gender as gender,1 as gender_group,:nickname as nickname1,:gender as gender1 ) 
            as t where gender in (1) and gender_group in (:gender_group)
        "#,
        [":nickname",":gender",":gender_group"]
    );
    let mut res=sqlx::query_as::<_, UserModel>(sql.as_str());
    model_sql_bind_match!(bind_res,{
        ":nickname":{
            res=res.bind(&nikename);
        }
        ,
        ":gender":{
            res=res.bind(&gender);
        },
        ":gender_group":{
            res=res.bind(gender_group.sql_quote());
        }
    });
    let res=res.fetch_one(&db).await.unwrap();
    assert_eq!(res.nickname,nikename.to_string());
    assert_eq!(res.gender,gender);
    
}