Databases 13 min read

Secure Rust‑MySQL Connections to TiDB Cloud: Drivers, ORM Examples, and Code

This article demonstrates how to securely connect Rust applications to TiDB Cloud Serverless Tier using various MySQL drivers and ORMs—including rust‑mysql‑simple, sqlx, SeaORM, and Rbatis—providing full code samples, dependency details, and practical insights.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
Secure Rust‑MySQL Connections to TiDB Cloud: Drivers, ORM Examples, and Code

Recently I experimented with integrating Rust and databases, using TiDB Cloud Serverless Tier as the data source. TiDB is a leading open‑source distributed database, and its serverless tier offers a convenient, cost‑effective cloud solution.

MySQL Rust Driver

The

rust-mysql-simple

crate provides a pure‑Rust MySQL driver.

<code>[dependencies]
# mysql origin
mysql = "*"
</code>

Example code for a secure connection:

<code>use chrono::Local;
use mysql::prelude::*;
use mysql::*;
use rbatis::snowflake::new_snowflake_id;
use serde::{Deserialize, 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";
    // Native connection
    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_statement = format!(
        "CREATE TABLE IF NOT EXISTS {} (\n        id BIGINT NOT NULL ,\n        name VARCHAR(128) NOT NULL,\n        gender TINYINT NOT NULL,\n        mobile VARCHAR(11) NOT NULL,\n        create_time DATETIME NOT NULL, \n        update_time DATETIME NOT NULL, \n        PRIMARY KEY(id)\n    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;",
        TABLE_NAME
    );
    conn_origin.query_drop(create_statement)?;

    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.clone(),
                "gender" => p.gender,
                "mobile" => p.mobile.clone(),
                "create" => p.create_time.as_ref(),
                "update" => p.update_time.as_ref()
            }
        })
    )?;

    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(())
}
</code>

The code creates

SslOpts

, builds connection options with

OptsBuilder

, and performs table creation, insertion, and selection over a secure TLS channel.

sqlx (Async)

sqlx

is an asynchronous Rust SQL crate.

<code>[dependencies]
# sqlx
sqlx = "0.6.2"
</code>

Example async connection:

<code>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);
    }
}
</code>

SeaORM

SeaORM builds on

sqlx

to provide an ORM layer.

<code>[dependencies]
# SeaORM
sqlx = "0.6.2"
sea-orm = { version = "0.10.6", features = ["sqlx-mysql", "runtime-async-std-native-tls", "macros"] }
</code>
<code>use sea_orm::ConnectionTrait;
use sea_orm::DbBackend;
use sea_orm::SqlxMySqlConnector;
use sea_orm::{FromQueryResult, Statement as sea_statement};
use sqlx::MySqlPool;

#[derive(Debug, FromQueryResult)]
pub struct SeaOrmBiz {
    pub id: i64,
    pub name: String,
    pub gender: Option<u8>,
    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 = MySqlPoolOptions::new()
        .connect_with(sqlx_opts)
        .await
        .unwrap();
    let db = SqlxMySqlConnector::from_sqlx_mysql_pool(pool);

    let rs = db.execute(sea_statement::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_statement::from_sql_and_values(DbBackend::MySql, r#"SELECT * FROM sample;"#, vec![])
    )
    .all(&db)
    .await
    .unwrap();
    println!(">>>>> select rs is {:?}", biz);
}
</code>

SeaORM uses

sqlx::MySqlConnectOptions

to build a pool, then creates a

SqlxMySqlConnector

for ORM operations.

Rbatis

Rbatis is a Rust ORM inspired by MyBatis.

<code>[dependencies]
# rbatis integration
rbs = "0.1.13"
rbatis = "4.0.44"
rbdc-mysql = "0.1.18"
</code>
<code>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};

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<String>,
    pub update_time: Option<String>,
}
rbatis::crud!(BizRbatis {}, TABLE_NAME);

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    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::<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::<String>(&sql_select_one, vec![to_value!(1)]).await;
    println!(">>>>> rbatis select result={:?}", row);
    Ok(())
}
</code>

Rbatis creates a struct per table, builds

MySqlConnectOptions

(similar to JDBC), and initializes the ORM with those options.

Postscript

I also attempted to use Diesel for a secure MySQL connection, but compilation failed on macOS due to missing

mysqlclient

libraries. Diesel relies heavily on C/C++ drivers, making it harder to set up in this environment.

Comparing SeaORM and Rbatis, SeaORM requires more boilerplate to define entities and relationships, while Rbatis offers a lighter mental model with one struct per table and flexible dynamic SQL via HtmlSql or PySql. SeaORM feels similar to Hibernate; Rbatis resembles MyBatis.

Databases remain a core external resource for applications, and I plan to explore more Rust‑database integration details in future posts.

See you next time.

RustMySQLsqlxrbatisseaormsecure connectiontidb cloud
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.