制作 Rust 语言异步 ORM 框架(Mybatis)第二弹

未阅读前篇的小伙伴可以先阅读第一篇,以了解Rbatis的心路历程
第一篇地址

Github源码链接rbatis

下面开始

第一篇我们讲到了,已经设计完了基本的ORM主体框架,这次带来的有
Wrapper,分页插件,逻辑删除插件

1 第一步 设计Wrapper。所谓Wrapper简单的说就是基本sql where语法的封装,可以在代码中直接new出来避免大量sql出现。

举个例子:

///         let w = Wrapper::new(&DriverType::Mysql)
///             .eq("id", 1)
///             .and()
///             .ne("id", 1)
///             .and()
///             .in_array("id", &[1, 2, 3])
///             .and()
///             .not_in("id", &[1, 2, 3])
///             .and()
///             .like("name", 1)
///             .or()
///             .not_like("name", "asdf")
///             .and()
///             .between("create_time", "2020-01-01 00:00:00", "2020-12-12 00:00:00")
///             .group_by(&["id"])
///             .order_by(true, &["id", "name"])
///             .check().unwrap();

我们要注意的是,设计Wrapper 必须带有语法检查,例如group_by和order_by关键字 在拼接的时候,必须检查前面的语法是否 以 where 结尾,如果是where结尾那么我们要删掉它,否则语法错误。

2 第二步,设计分页插件.分页插件会自动分析你写的sql或者wrapper,自动把sql语句拆分为 count语句计算总数和select语句筛选数据。

首先定义接口:

pub trait PagePlugin: Send + Sync {
    /// return 2 sql for select ,  (count_sql,select_sql)
    fn create_page_sql(&self, driver_type: &DriverType, tx_id: &str, sql: &str, args: &Vec<serde_json::Value>, page: &dyn IPageRequest) -> Result<(String, String), rbatis_core::Error>;
}

定义Ipage抽象接口

pub trait IPage<T>: IPageRequest {
    fn get_records(&self) -> &Vec<T>;
    fn get_records_mut(&mut self) -> &mut Vec<T>;
    fn set_records(&mut self, arg: Vec<T>);
   ///计算总页码数 pages
    fn get_pages(&self) -> u64 {
        if self.get_size() == 0 {
            return 0;
        }
        let mut pages = self.get_total() / self.get_size();
        if self.get_total() % self.get_size() != 0 {
            pages = pages + 1;
        }
        return pages;
    }
    ///sum offset 计算 开始的页码索引值
    fn offset(&self) -> u64 {
        if self.get_current() > 0 {
            (self.get_current() - 1) * self.get_size()
        } else {
            0
        }
    }
}

定义Page对象

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Page<T> {
    ///data
    pub records: Vec<T>,
    ///total num
    pub total: u64,
    ///default 10
    pub size: u64,
    ///current index
    pub current: u64,

    pub serch_count: bool,
}

实现分页逻辑,就是把 单条sql 拆分为 count操作和select操作执行,最后返回Page对象

impl PagePlugin for RbatisPagePlugin {
    fn create_page_sql<>(&self, driver_type: &DriverType, tx_id: &str, sql: &str, args: &Vec<Value>, page: &dyn IPageRequest) -> Result<(String, String), rbatis_core::Error> {
        let mut sql = sql.to_owned();
        sql = sql.replace("select ", "SELECT ");
        sql = sql.replace("from ", "FROM ");
        sql = sql.trim().to_string();
        let limit_sql = driver_type.page_limit_sql(page.offset(), page.get_size())?;
        sql = sql + limit_sql.as_str();
        if !sql.starts_with("SELECT ") && !sql.contains("FROM ") {
            return Err(rbatis_core::Error::from("[rbatis] xml_fetch_page() sql must contains 'select ' And 'from '"));
        }
        let mut count_sql = sql.clone();
        if page.is_serch_count() {
            //make count sql
            let sql_vec: Vec<&str> = count_sql.split("FROM ").collect();
            count_sql = "SELECT count(1) FROM ".to_string() + sql_vec[1];
        }
        return Ok((count_sql, sql));
    }
}

最后,抽象插件定义在Rbatis成员中

/// rbatis engine
pub struct Rbatis<'r> {
    ...
    /// page plugin,动态类型的插件 
    pub page_plugin: Box<dyn PagePlugin>
}

我们使用分页的时候就变成了

 let w = Wrapper::new(&rb.driver_type().unwrap())
            .eq("delete_flag",1)
            .check().unwrap();
        let r: Page<BizActivity> = rb.fetch_page_by_wrapper(&w, &PageRequest::new(1, 20)).await.unwrap();

//执行结果

2020-07-05T23:38:16.348674800+08:00 INFO rbatis::rbatis - [rbatis] Query ==> SELECT count(1) FROM biz_activity WHERE delete_flag = 1  AND delete_flag =  ? LIMIT 0,20
2020-07-05T23:38:16.350675400+08:00 INFO rbatis::rbatis - [rbatis] Args  ==> [1]
2020-07-05T23:38:16.370671900+08:00 INFO rbatis::rbatis - [rbatis] Total <== 1
2020-07-05T23:38:16.370671900+08:00 INFO rbatis::rbatis - [rbatis] Query ==> SELECT  create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version  FROM biz_activity WHERE delete_flag = 1  AND delete_flag =  ? LIMIT 0,20
2020-07-05T23:38:16.370671900+08:00 INFO rbatis::rbatis - [rbatis] Args  ==> [1]
2020-07-05T23:38:16.373696300+08:00 INFO rbatis::rbatis - [rbatis] Total <== 5
{
    "records": [{
        "id": "12312",
        "name": "null",
        "pc_link": "null",
        "h5_link": "null",
        "pc_banner_img": "null",
        "h5_banner_img": "null",
        "sort": "null",
        "status": 1,
        "remark": "null",
        "create_time": "2020-02-09 00:00:00 UTC",
        "version": 1,
        "delete_flag": 1
    }],
    "total": 5,
    "size": 20,
    "current": 1,
    "serch_count": true
}

3 设计 逻辑删除插件
定义接口

/// Logic Delete Plugin trait
pub trait LogicDelete: Send + Sync {
    //逻辑删除的数据库字段
    fn column(&self) -> &str;
    //删除标志
    fn deleted(&self) -> i32;
    fn un_deleted(&self) -> i32;
    //创建删除时生成的sql
    fn create_sql(&self, driver_type: &DriverType, table_name: &str, sql_where: &str) -> Result<String, rbatis_core::Error>;
}

配合wrapper拦截删除操作改为update 操作

    async fn remove_by_id<T>(&self, id: &T::IdType) -> Result<u64> where T: CRUDEnable {
        let mut sql = String::new();
        if self.logic_plugin.is_some() {
            sql = self.logic_plugin.as_ref().unwrap().create_sql(&self.driver_type()?, T::table_name().as_str(), format!(" WHERE id = {}", id).as_str())?;
        } else {
            sql = format!("DELETE FROM {} WHERE id = {}", T::table_name(), id);
        }
        return self.exec_prepare("", sql.as_str(), &vec![]).await;
    }

最后使用的时候变成了

            let mut rb = Rbatis::new();
            rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
            //设置 逻辑删除插件
            rb.logic_plugin = Some(Box::new(RbatisLogicDeletePlugin::new("delete_flag")));
            //执行逻辑删除
            let r = rb.remove_by_id::<BizActivity>(&"1".to_string()).await;
            if r.is_err() {
                println!("{}", r.err().unwrap().to_string());
            }

返回结果

2020-07-05T23:22:51.235834600+08:00 INFO rbatis::rbatis - [rbatis] Exec ==> UPDATE biz_activity SET delete_flag = 0 WHERE id = 1

2020-07-05T23:18:34.426681800+08:00 INFO rbatis::rbatis - [rbatis] Total <== 1

最后,我们的框架基本功能已经完善,剩下的就是完善文档以及投入生产环境使用啦。慢慢享受rust带来的稳定和高性能~

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!