乐趣区

关于rust:文盘Rust-安全连接-TiDBMysql

作者:京东科技 贾世闻

最近在折腾 rust 与数据库集成,为了偷懒,选了 Tidb Cloud Serverless Tier 作为数据源。Tidb 无疑是近五年来最优良的国产开源分布式数据库,Tidb Cloud Serverless Tier 作为 pingcap 旗下的云产品不便又经济,这次应用还有一些小惊喜,这个后文再说。

Tidb Cloud Serverless Tier 的应用文档还是很全面的,详细情况请参考应用 TiDB Cloud (Serverless Tier) 构建 TiDB 集群.

集群建设实现后,Tidb Cloud Serverless Tier 有个小性能是能够显示支流客户端以及风行编程语言的连贯代码。包含: MysqlCli、MyCli、JDBC、Python、golang 以及 Nodejs。

嗯?rust 的代码在哪儿?很遗憾没有 rust 的代码。而且为了平安起见,Tidb Cloud Serverless Tier 貌似只反对平安连贯。在查找文档过程中 rust 的 数据库驱动和很多 orm 文档中也没有对于平安具体的形容,不少思路是在 issues 外面给出的。索性把 rust 连贯 mysql 支流形式的平安连贯代码都记录下来,一来给本人留个备忘,二来给须要的同学做个提醒。

以下实例所应用的的标的建表语句如下

CREATE TABLE IF NOT EXISTS sample (
        id BIGINT NOT NULL ,
        name VARCHAR(128) NOT NULL,
        gender TINYINT NOT NULL,
        mobile VARCHAR(11) NOT NULL,
        create_time DATETIME NOT NULL, 
        update_time DATETIME NOT NULL, 
        PRIMARY KEY(id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

mysql rust driver

rust-mysql-simple, 纯 rust 实现的 mysql 驱动。

  • 依赖

    [dependencies]
    # mysql origin
    mysql = "*"
    
  • 代码

    use chrono::Local;
    use mysql::prelude::*;
    use mysql::*;
    use rbatis::snowflake::new_snowflake_id;
    use serde::Deserialize;
    use serde::Serialize;
    
    pub const TABLE_NAME: &str = "sample";
    
    #[derive(Clone, Debug, Serialize, Deserialize)]
    pub struct BizOrigin {
        pub id: i64,
        pub name: String,
        pub gender: u8,
        pub mobile: String,
        pub create_time: Option<String>,
        pub update_time: Option<String>,
    }
    
    fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
        let fmt = "%Y-%m-%d %H:%M:%S";
        // 原生形式连贯
        let cert_path = std::path::Path::new("/etc/ssl/cert.pem");
        let ssl_opts = SslOpts::default().with_root_cert_path(Some(cert_path));
        let opts = OptsBuilder::new()
            .ip_or_hostname(Some("gateway01.us-east-19.prod.aws.tidbcloud.com"))
            .tcp_port(4000)
            .user(Some("tidbcloudtier.root"))
            .pass(Some("xxxxxxxxxxxx"))
            .ssl_opts(ssl_opts)
            .db_name(Some("test"));
    
        let mut conn_origin = Conn::new(opts)?;
        let (_, cipher_origin): (Value, String) = "SHOW STATUS LIKE'Ssl_cipher'"
            .first(&mut conn_origin)?
            .unwrap();
        println!(">>>>> Cipher in use from origin: {}", cipher_origin);
    
        let create_statment = format!(
            "
        CREATE TABLE IF NOT EXISTS {} (
            id BIGINT NOT NULL ,
            name VARCHAR(128) NOT NULL,
            gender TINYINT NOT NULL,
            mobile VARCHAR(11) NOT NULL,
            create_time DATETIME NOT NULL, 
            update_time DATETIME NOT NULL, 
            PRIMARY KEY(id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;",
            TABLE_NAME
        );
        conn_origin.query_drop(create_statment)?;
    
        let bizes = vec![
            BizOrigin {id: new_snowflake_id(),
                name: "Bob".to_string(),
                gender: 1,
                mobile: "13037777876".to_string(),
                create_time: Some(Local::now().format(fmt).to_string()),
                update_time: Some(Local::now().format(fmt).to_string()),
            },
            BizOrigin {id: new_snowflake_id(),
                name: "Jecika".to_string(),
                gender: 0,
                mobile: "13033457876".to_string(),
                create_time: Some(Local::now().format(fmt).to_string()),
                update_time: Some(Local::now().format(fmt).to_string()),
            },
        ];
    
        conn_origin.exec_batch(r"insert into sample (id,name,gender,mobile,create_time,update_time) 
        values (:id,:name,:gender,:mobile,:create,:update)",
            bizes.iter().map(|p| -> Params {
                params! {
                    "id"=>p.id,
                    "name"=>p.name.to_owned(),
                    "gender"=>p.gender.to_owned(),
                    "mobile"=>p.mobile.to_owned(),
                    "create"=>p.create_time.as_ref(),
                    "update"=>p.update_time.as_ref()}
            }),
        )?;
    
        // Let's select payments from database. Type inference should do the trick here.
        let selected_bizs = conn_origin.query_map(
            "SELECT id,name,gender,mobile,create_time,update_time from sample",
            |(id, name, gender, mobile, create_time, update_time)| BizOrigin {
                id,
                name,
                gender,
                mobile,
                create_time,
                update_time,
            },
        )?;
        println!("selected result {:?}", selected_bizs);
    
        Ok(())
    }
    

代码并不简单,首先创立 SslOpts,指定 CA 文件的地位;而后应用 OptsBuilder 生成链接配置信息;最初创立 Connection。前面是执行表创立以及验证链接,最初是对标的 insert 和 select 操作。

sqlx

sqlx 是纯 Rust 编写的异步 SQL Crate。

  • 依赖

    [dependencies]
    # sqlx
    sqlx = "0.6.2"
    
  • 代码

    use futures::TryStreamExt;
    use sqlx::mysql::MySqlPoolOptions;
    
    #[tokio::main]
    async fn main() {let sqlx_opts = sqlx::mysql::MySqlConnectOptions::new()
            .host("gateway01.us-east-19.prod.aws.tidbcloud.com")
            .port(4000)
            .database("test")
            .username("tidbcloudtier.root")
            .password("xxxxxxxxxxxx")
            .ssl_ca("/etc/ssl/cert.pem");
    
        let pool = MySqlPoolOptions::new()
            .connect_with(sqlx_opts)
            .await
            .unwrap();  
    
        let mut rows = sqlx::query("select * from sample").fetch(&pool);
        while let Some(row) = rows.try_next().await.unwrap() {println!("row is {:?}", row);
        }
    }
    

SeaORM

SeaORM 是在 sqlx 之上构建的 orm 框架。

  • 依赖

    [dependencies]
    # SeaORM
    sqlx = "0.6.2"
    sea-orm = {version = "0.10.6", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "macros"] }
    
  • 代码

    use sea_orm::ConnectionTrait;
    use sea_orm::DbBackend;
    use sea_orm::SqlxMySqlConnector;
    use sea_orm::{FromQueryResult, Statement as sea_statment};
    use sqlx::MySqlPool;
    
    #[derive(Debug, FromQueryResult)]
    pub struct SeaOrmBiz {
        pub id: i64,
        pub name: String,
        pub gender: Option<i8>,
        pub mobile: String,
        pub create_time: chrono::NaiveDateTime,
        pub update_time: chrono::NaiveDateTime,
    }
    
    #[tokio::main]
    async fn main() {let sqlx_opts = sqlx::mysql::MySqlConnectOptions::new()
            .host("gateway01.us-east-19.prod.aws.tidbcloud.com")
            .port(4000)
            .database("test")
            .username("tidbcloudtier.root")
            .password("xxxxxxxxx")
            .ssl_ca("/etc/ssl/cert.pem");
    
        let pool = MySqlPool::connect_with(sqlx_opts).await.unwrap();
        let db = SqlxMySqlConnector::from_sqlx_mysql_pool(pool);
    
        let rs = db
            .execute(sea_statment::from_string(db.get_database_backend(),
                "select 1 from dual;".to_string(),))
            .await;
        println!(">>>>> Cipher in use from sea_orm:{:?}", rs);
    
        let biz: Vec<SeaOrmBiz> = SeaOrmBiz::find_by_statement(sea_statment::from_sql_and_values(
            DbBackend::MySql,
            r#"SELECT * FROM sample;"#,
            vec![],))
        .all(&db)
        .await
        .unwrap();
        println!(">>>>> selet rs is {:?}", biz);
    }
    

SeaOrm 依赖 sqlx。首先构建 sqlx::MySqlConnectOptions 而后依据 MySqlConnectOptions 构建 sqlx::MySqlPool 最初构建 sea_orm::SqlxMySqlConnector 用于与 mysql 通信。

Rbatis

rbatis

  • 依赖

    [dependencies]
    # rbatis integration
    rbs = "0.1.13"
    rbatis = "4.0.44"
    rbdc-mysql = "0.1.18"
    
  • 代码

    use rbatis::rbdc::datetime::FastDateTime;
    use rbatis::Rbatis;
    use rbdc_mysql::options::MySqlConnectOptions;
    use rbdc_mysql::{driver::MysqlDriver, options::MySqlSslMode as rbdc_MysqlSslMode};
    use rbs::to_value;
    use serde::{Deserialize, Serialize};
    use std::collections::HashMap;
    
    pub const TABLE_NAME: &str = "sample";
    
    #[derive(Clone, Debug, Serialize, Deserialize)]
    pub struct BizRbatis {
        pub id: Option<i64>,
        pub name: Option<String>,
        pub gender: Option<u8>,
        pub mobile: Option<String>,
        pub create_time: Option<FastDateTime>,
        pub update_time: Option<FastDateTime>,
    }
    rbatis::crud!(BizRbatis {}, TABLE_NAME);
    
    #[tokio::main]
    async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
        // rbatis 连贯
        let rb = Rbatis::new();
        let opt = MySqlConnectOptions::new()
            .host("gateway01.us-east-19.prod.aws.tidbcloud.com")
            .port(4000)
            .database("test")
            .username("tidbcloudtier.root")
            .password("xxxxxxxxxx")
            .ssl_mode(rbdc_MysqlSslMode::VerifyIdentity)
            .ssl_ca("/etc/ssl/cert.pem");
        rb.init_opt(MysqlDriver {}, opt).unwrap();
        rb.get_pool().unwrap().resize(3);
    
        let sql_show_ssl_cipher = "SHOW STATUS LIKE'Ssl_cipher'";
    
        let cipher_rbatis = rb
            .fetch_decode::<Vec<HashMap<String, String>>>(sql_show_ssl_cipher, vec![])
            .await;
    
        println!(">>>>> Cipher in use from rbatis: {:?}", cipher_rbatis);
    
        let sql_select_one = format!("select * from {} limit ?;", TABLE_NAME);
        let row = rb
            .fetch_decode::<BizRbatis>(&sql_select_one, vec![to_value!(1)])
            .await;
        println!(">>>>> rbatsis select result={:?}", row);
    
        Ok(())
    }
    

首先,新建一个 Rbatis struct; 构建 rbdc\_mysql::options::MySqlConnectOptions (rbdc 相当于 java 体系里的 jdbc,是 rbatis 的衍生我的项目); 最初通过配置好的 rbdc\_mysql::options::MySqlConnectOptions 初始化 Rbatis。

后记

在这次试验中笔者也试图应用 Diesel 建设 mysql 平安连贯,不过在编译的时候失败,未入门先放弃。Diesel 因为开发工夫长远,彼时各个数据库的 rust 原生驱动缺失,所以大量才用 c/c++ driver 进行构建,这次编译失败也是因为在 macos 上找不到 mysqlclient 导致。有对 Diesel 强依赖的同学能够持续摸索。再来说说对 SeaOrm 和 Rbatis 的直观感触。SeaOrm 构建实体比拟麻烦,如果不是通过工具手工构建实体比拟烧脑;实体中蕴含各种与其余实体的关系;动静 sql 能够通过 sea_query 工具包来构建。Rbatis 构建实体心智累赘就小很多,一张表一个实体;动静 sql 能够通过 HtmlSql 和 PySql 实现,sql 与代码充沛解耦。rbdc 作为 Rbatis 的衍生我的项目,显然是要做 rust 生态的 JDBC。从感觉上来讲 SeaOrm 更像 hibernate;而 Rbatis 是复刻 Mybatis。数据库是应用程序打交道最多的内部资源,相干话题也很多,有机会再和大家聊聊 rust 与 数据库打交道的更多细节。

咱们下期见。

退出移动版