作者:京东科技 贾世闻

最近在折腾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 originmysql = "*"
  • 代码

    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]# sqlxsqlx = "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]# SeaORMsqlx = "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 integrationrbs = "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 与 数据库打交道的更多细节。

咱们下期见。