Database Libraries
Libraries for database connectivity and ORM in Rust.
sqlx
Async, compile-time checked SQL queries.
[dependencies]
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }
tokio = { version = "1", features = ["full"] }
Connection
use sqlx::postgres::PgPoolOptions;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect("postgres://user:pass@localhost/db")
.await?;
Ok(())
}
Compile-Time Checked Queries
use sqlx::FromRow;
#[derive(FromRow)]
struct User {
id: i32,
name: String,
email: String,
}
async fn get_user(pool: &PgPool, id: i32) -> Result<User, sqlx::Error> {
sqlx::query_as!(User, "SELECT id, name, email FROM users WHERE id = $1", id)
.fetch_one(pool)
.await
}
Runtime Queries
use sqlx::{Row, FromRow};
async fn dynamic_query(pool: &PgPool) -> Result<Vec<User>, sqlx::Error> {
let users = sqlx::query_as::<_, User>("SELECT * FROM users")
.fetch_all(pool)
.await?;
Ok(users)
}
async fn get_scalar(pool: &PgPool) -> Result<i64, sqlx::Error> {
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users")
.fetch_one(pool)
.await?;
Ok(count.0)
}
Transactions
async fn transfer(pool: &PgPool, from: i32, to: i32, amount: i64) -> Result<(), sqlx::Error> {
let mut tx = pool.begin().await?;
sqlx::query("UPDATE accounts SET balance = balance - $1 WHERE id = $2")
.bind(amount)
.bind(from)
.execute(&mut *tx)
.await?;
sqlx::query("UPDATE accounts SET balance = balance + $1 WHERE id = $2")
.bind(amount)
.bind(to)
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(())
}
Migrations
# Install CLI
cargo install sqlx-cli
# Create migration
sqlx migrate add create_users
# Run migrations
sqlx migrate run
// Run migrations in code
sqlx::migrate!("./migrations")
.run(&pool)
.await?;
diesel
Synchronous ORM with type-safe query builder.
[dependencies]
diesel = { version = "2", features = ["postgres"] }
dotenvy = "0.15"
Schema
// schema.rs (generated by diesel)
diesel::table! {
users (id) {
id -> Int4,
name -> Varchar,
email -> Varchar,
}
}
Models
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::users)]
struct User {
id: i32,
name: String,
email: String,
}
#[derive(Insertable)]
#[diesel(table_name = crate::schema::users)]
struct NewUser<'a> {
name: &'a str,
email: &'a str,
}
Queries
use diesel::prelude::*;
fn get_users(conn: &mut PgConnection) -> QueryResult<Vec<User>> {
use crate::schema::users::dsl::*;
users.load(conn)
}
fn get_user_by_id(conn: &mut PgConnection, user_id: i32) -> QueryResult<User> {
use crate::schema::users::dsl::*;
users.find(user_id).first(conn)
}
fn create_user(conn: &mut PgConnection, new_user: NewUser) -> QueryResult<User> {
use crate::schema::users;
diesel::insert_into(users::table)
.values(&new_user)
.returning(User::as_returning())
.get_result(conn)
}
sea-orm
Async ORM inspired by ActiveRecord.
[dependencies]
sea-orm = { version = "0.12", features = ["runtime-tokio-native-tls", "sqlx-postgres"] }
Entity
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub email: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
Queries
use sea_orm::*;
async fn get_users(db: &DatabaseConnection) -> Result<Vec<user::Model>, DbErr> {
User::find().all(db).await
}
async fn get_user(db: &DatabaseConnection, id: i32) -> Result<Option<user::Model>, DbErr> {
User::find_by_id(id).one(db).await
}
async fn create_user(db: &DatabaseConnection, name: &str, email: &str) -> Result<user::Model, DbErr> {
let new_user = user::ActiveModel {
name: Set(name.to_owned()),
email: Set(email.to_owned()),
..Default::default()
};
new_user.insert(db).await
}
rusqlite
SQLite bindings for Rust.
[dependencies]
rusqlite = { version = "0.31", features = ["bundled"] }
use rusqlite::{Connection, Result};
fn main() -> Result<()> {
let conn = Connection::open("my_db.db")?;
conn.execute(
"CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
)",
[],
)?;
conn.execute(
"INSERT INTO users (name, email) VALUES (?1, ?2)",
["Alice", "alice@example.com"],
)?;
let mut stmt = conn.prepare("SELECT id, name, email FROM users")?;
let users = stmt.query_map([], |row| {
Ok(User {
id: row.get(0)?,
name: row.get(1)?,
email: row.get(2)?,
})
})?;
for user in users {
println!("{:?}", user?);
}
Ok(())
}
redis
Redis client for Rust.
[dependencies]
redis = { version = "0.25", features = ["tokio-comp"] }
use redis::AsyncCommands;
#[tokio::main]
async fn main() -> redis::RedisResult<()> {
let client = redis::Client::open("redis://127.0.0.1/")?;
let mut con = client.get_multiplexed_async_connection().await?;
// Set a value
con.set("key", "value").await?;
// Get a value
let value: String = con.get("key").await?;
// Set with expiry
con.set_ex("temp_key", "temp_value", 60).await?;
// Hash operations
con.hset("user:1", "name", "Alice").await?;
let name: String = con.hget("user:1", "name").await?;
Ok(())
}
Comparison
| Crate | Type | Async | Compile-Time Check |
|---|---|---|---|
| sqlx | Raw SQL | Yes | Yes |
| diesel | ORM | No | Yes |
| sea-orm | ORM | Yes | No |
| rusqlite | Raw SQL | No | No |
Connection Pooling
sqlx Pool
let pool = PgPoolOptions::new()
.max_connections(10)
.min_connections(2)
.acquire_timeout(Duration::from_secs(30))
.connect(&database_url)
.await?;
diesel + r2d2
use diesel::r2d2::{ConnectionManager, Pool};
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = Pool::builder()
.max_size(10)
.build(manager)?;
let conn = pool.get()?;
Summary
| Crate | Use Case |
|---|---|
| sqlx | Async SQL with compile-time checks |
| diesel | Type-safe ORM |
| sea-orm | Async ActiveRecord-style ORM |
| rusqlite | SQLite |
| redis | Redis client |
Choosing a Database Library
| Need | Recommendation |
|---|---|
| Compile-time SQL checking | sqlx |
| Full ORM with sync | diesel |
| Full ORM with async | sea-orm |
| SQLite embedded | rusqlite |
| Key-value cache | redis |