作者:京东科技 贾世闻

最近在折腾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 与 数据库打交道的更多细节。

咱们下期见。

文盘Rust -- 安全连接 TiDB/Mysql的更多相关文章

  1. 文盘Rust -- rust 连接云上数仓 starwift

    作者:京东云 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番. ...

  2. 文盘Rust -- 本地库引发的依赖冲突

    作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs .clickhouse-rs 是 tcp ...

  3. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  4. 文盘Rust -- 把程序作为守护进程启动

    当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...

  5. 文盘Rust -- 给程序加个日志

    作者:贾世闻 日志是应用程序的重要组成部分.无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录.在这篇文章中,我们结合[log4rs](https://github.com/estk/l ...

  6. 文盘Rust -- 用Tokio实现简易任务池

    作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...

  7. 【转帖】解决远程连接MariaDB(mysql)很慢的方法

    在CentOS7上安装完成MariaDB之后,发现无论命令行还是程序中连接MariaDB的时候都很慢,大约要一二十秒,于是网上搜索了一番,发现下面的文章内容: 在进行 ping和route后发现网络通 ...

  8. RUST actix-web连接有密码的Redis数据库

    RUST actix-web连接有密码的Redis数据库 actix-web的example里面,使用了自己的actix-redis,但是我尝试了一下,并不好用 替换成另一连接池,deadpool-r ...

  9. day128:MySQL进阶:MySQL安装&用户/权限/连接/配置管理&MySQL的体系结构&SQL&MySQL索引和执行计划

    目录 1.介绍和安装 2.基础管理 2.1 用户管理 2.2 权限管理 2.3 连接管理 2.4 配置管理 3.MySQL的体系结构 4.SQL 5.索引和执行计划 1.介绍和安装 1.1 数据库分类 ...

  10. 解决Mysql连接池被关闭 ,hibernate尝试连接不能连接的问题。 (默认mysql连接池可以访问的时间为8小时,如果超过8小时没有连接,mysql会自动关闭连接池。系统发布第二天访问链接关闭问题。

    解决Mysql连接池被关闭  ,hibernate尝试连接不能连接的问题. (默认MySQL连接池可以访问的时间为8小时,如果超过8小时没有连接,mysql会自动关闭连接池. 所以系统发布第二天访问会 ...

随机推荐

  1. 云数据库 GaussDB(for Influx) 解密第十一期:让智能电网中时序数据处理更高效

    摘要:GaussDB(for Influx)是一款基于计算存储分离架构,完全兼容 InfluxDB 生态的云原生时序数据库. 本文分享自华为云社区<云数据库 GaussDB(for Influx ...

  2. ios安全加固 ios 加固方案

    ​ 目录 一.iOS加固保护原理 1.字符串混淆 2.类名.方法名混淆 3.程序结构混淆加密 4.反调试.反注入等一些主动保护策略 二 代码混淆步骤 1. 选择要混淆保护的ipa文件 2. 选择要混淆 ...

  3. Docker--简介&&安装

    Docker 是一种应用容器引擎 一 容器 Linux系统提供了Namespace和Cgroup技术实现环境隔离和资源控制 其中Namespace是Linux提供的一种内核级别环境隔离的方法,能使一个 ...

  4. 用 WebRTC 打造一个音乐教育 App,要解决哪些音质难题?

    在去年疫情期间,在线教育行业获得了井喷式的发展,这背后的技术功臣非 RTC 莫属.本文将分享 RTC 技术在音乐教育场景下的实践经验. 作者| 逸城 审校| 泰一 音乐教育场景 - 在线陪练 2020 ...

  5. CMake 禁用 MSVC 编译警告 C4819

    warning C4819:该文件包含不能在当前代码页(936)中表示的字符.请将该文件保存为 Unicode 格式以防止数据丢失 if (win32) add_complie_options(/W4 ...

  6. POJ - 1113 Wall (凸包模板) Graham Scan 算法实现

    Description Once upon a time there was a greedy King who ordered his chief Architect to build a wall ...

  7. 记一次 .NET某道闸收费系统 内存溢出分析

    一:背景 1. 讲故事 前些天有位朋友找到我,说他的程序几天内存就要爆一次,不知道咋回事,找不出原因,让我帮忙看一下,这种问题分析dump是最简单粗暴了,拿到dump后接下来就是一顿分析. 二:Win ...

  8. C#开源跨平台的多功能Steam工具箱&GitHub加速神器

    前言 作为一个程序员你是否会经常会遇到GitHub无法访问(如下无法访问图片),或者是访问和下载源码时十分缓慢就像乌龟爬行一般.今天分享一款C#开源的.跨平台的多功能Steam工具箱和GitHub加速 ...

  9. Android Emulator 画面闪烁

    Android 虚拟机经常会出现画面闪烁,可以通过修改设置解决. 打开虚拟机之后,点击 "..." 按钮.按照下图改为 "D3D11",冷重启虚拟机就好了.

  10. (已解决)pulse secure 连接功能变灰禁用 连接面板找不到

    今天打开 pulse secure 时,发现窗口变成了这样: 连接功能是灰色的,被禁用了: 解决方案: 运行 PulseSecureService 服务. 然后就正常了!