rust-database
Skill1 starsChanged 6 months agoMITView on GitHub
Rust database development with SQLx, SeaORM, Diesel, transactions, migrations, and pooling. Use when: - User works with PostgreSQL, MySQL, SQLite, Redis, or database-backed Rust services - User uses sqlx, sea-orm, diesel, refinery, sea-orm-migration, deadpool, or bb8 - User designs repositories, transactions, migrations, schema evolution, or connection pools - User asks about compile-time checked SQL, query_as, FromRow, lifetimes in row mapping, or DB errors - User debugs pool exhaustion, deadlocks, slow queries, N+1 queries, or transaction boundaries - User needs test database setup, fixtures, migrations in CI, or database integration tests - User needs Redis caching, pub/sub, or session storage patterns
4 other projects use a version of this.
Adopted by
Most recently taken up by
- po4yka/rust-skillstheir own version, first seen Nov 2, 2026
- full-stack-skills/rust-skillstheir own version, first seen Oct 15, 2026
- dawidpereira/rust-skillstheir own version, first seen Mar 22, 2026
- huiali/rust-skillstheir own version, first seen Feb 9, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- huiali/rust-skills★ 47same name
- full-stack-skills/rust-skills★ 8same name
- po4yka/rust-skills★ 5same name
- dawidpereira/rust-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cliSkill
- rust-cloud-nativeSkill
- rust-concurrencySkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-fintechSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-design-patternsSkill
- rust-performanceSkill
- rust-securitySkill
- rust-testingSkill
- rust-type-systemSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 7 are distinctive, and 4 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
7
Distinctive
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 26
Keep pool size smaller than database capacity.
- Distinctive: line 27
Prefer typed IDs and domain types over raw primitives.
- Distinctive: line 29
Test against the real database engine when behavior depends on SQL semantics.
- Distinctive: line 30
Never interpolate user input into raw SQL strings.
- Distinctive: line 108
Keep repositories thin: SQL mapping, transaction participation, and database-specific errors.
- Very specific: line 112
When column lists vary at runtime, use query_as with a raw string:
- Very specific: line 134
Use FromRow derive for custom result types.
- Very specific: line 150
Commit only after all domain writes succeed.
- Distinctive: line 206
Use keyset (cursor) pagination for large datasets instead of offset-based pagination, which degrades on deep pages.
- Very specific: line 276
Use SQLx's built-in #[sqlx::test] for per-test database transactions that roll back automatically.
- Distinctive: line 333
Every multi-write business operation has an explicit transaction boundary.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Rust Database Engineering
Practical guidance for safe, observable, and testable database-backed Rust services.
Quick Navigation
- references/sqlx.md - SQLx patterns, checked queries, pools, row mapping
- references/migrations.md - migrations, schema evolution, test databases, CI
Golden Rules
- Make transaction boundaries explicit.
- Keep pool size smaller than database capacity.
- Prefer typed IDs and domain types over raw primitives.
- Measure query plans before adding indexes.
- Test against the real database engine when behavior depends on SQL semantics.
- Never interpolate user input into raw SQL strings.
SQLx Setup
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "uuid", "chrono", "migrate"] }
uuid = { version = "1", features = ["serde", "v7"] }
thiserror = "1"use sqlx::{PgPool, postgres::PgPoolOptions};
pub async fn connect(database_url: &str) -> Result<PgPool, sqlx::Error> {
PgPoolOptions::new()
.max_connections(20)
.min_connections(2)
.connect(database_url)
.await
}Typed Repository Boundary
use sqlx::{PgPool, Postgres, Transaction};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(pub Uuid);
#[derive(Debug)]
pub struct User {
pub id: UserId,
pub email: String,
}
pub struct UsersRepo {
pool: PgPool,
}
impl UsersRepo {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
pub async fn find_by_email(&self, email: &str) -> Result<Option<User>, sqlx::Error> {
let row = sqlx::query!(
r#"select id, email from users where email = $1"#,
email
)
.fetch_optional(&self.pool)
.await?;
Ok(row.map(|row| User {
id: UserId(row.id),
email: row.email,
}))
}
pub async fn create_in_tx(
tx: &mut Transaction<'_, Postgres>,
email: &str,
) -> Result<UserId, sqlx::Error> {
let id = Uuid::now_v7();
sqlx::query!(
r#"insert into users (id, email) values ($1, $2)"#,
id,
email
)
.execute(&mut **tx)
.await?;
Ok(UserId(id))
}
}Keep repositories thin: SQL mapping, transaction participation, and database-specific errors. Put business decisions in services.
Dynamic Queries with query_as
When column lists vary at runtime, use query_as with a raw string:
use sqlx::FromRow;
#[derive(Debug, FromRow)]
pub struct UserSummary {
pub id: Uuid,
pub email: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}
pub async fn search_users(pool: &PgPool, query: &str) -> Result<Vec<UserSummary>, sqlx::Error> {
sqlx::query_as::<_, UserSummary>(
r#"select id, email, created_at from users where email like $1"#
)
.bind(format!("%{}%", query))
.fetch_all(pool)
.await
}Use FromRow derive for custom result types. Prefer compile-time query! when the SQL is static.
Transaction Pattern
pub async fn register_user(pool: &PgPool, email: &str) -> Result<UserId, AppError> {
let mut tx = pool.begin().await?;
let id = UsersRepo::create_in_tx(&mut tx, email).await?;
AuditRepo::insert(&mut tx, "user.registered", id.0).await?;
tx.commit().await?;
Ok(id)
}Commit only after all domain writes succeed. Rollback is automatic when tx drops, but explicit control flow is easier to review.
Error Mapping
#[derive(thiserror::Error, Debug)]
pub enum AppError {
#[error("database unavailable")]
Database(#[from] sqlx::Error),
#[error("email already exists")]
DuplicateEmail,
}
fn map_db_error(err: sqlx::Error) -> AppError {
if let sqlx::Error::Database(db) = &err {
if db.constraint() == Some("users_email_key") {
return AppError::DuplicateEmail;
}
}
AppError::Database(err)
}Map constraint names into domain errors at the boundary. Do not leak raw database messages to users.
Pool and Query Observability
let pool = PgPoolOptions::new()
.max_connections(20)
.acquire_timeout(std::time::Duration::from_secs(2))
.connect(database_url)
.await?;Track pool wait time, query latency, error count, rows affected, and transaction duration. Pool exhaustion often means slow queries, leaked transactions, or too much concurrency.
Pagination Pattern
pub async fn list_users(
pool: &PgPool,
page: i64,
page_size: i64,
) -> Result<Vec<User>, sqlx::Error> {
sqlx::query_as::<_, User>(
r#"select id, email from users order by id limit $1 offset $2"#
)
.bind(page_size)
.bind(page * page_size)
.fetch_all(pool)
.await
}Use keyset (cursor) pagination for large datasets instead of offset-based pagination, which degrades on deep pages.
Redis Caching
use redis::AsyncCommands;
pub struct CacheService {
client: redis::Client,
}
impl CacheService {
pub async fn get_or_compute<T, F>(
&self,
key: &str,
ttl_secs: usize,
compute: F,
) -> Result<T, Error>
where
T: serde::de::DeserializeOwned + serde::Serialize,
F: Future<Output = Result<T, Error>>,
{
let mut conn = self.client.get_multiplexed_async_connection().await?;
// Try cache first
if let Some(val) = conn.get::<_, Option<String>>(key).await? {
return Ok(serde_json::from_str(&val)?);
}
// Compute and cache
let val = compute().await?;
let encoded = serde_json::to_string(&val)?;
let _: () = conn.set_ex(key, encoded, ttl_secs).await?;
Ok(val)
}
}Database Testing
#[cfg(test)]
mod tests {
use super::*;
use sqlx::PgPool;
use sqlx::migrate::Migrator;
static MIGRATOR: Migrator = sqlx::migrate!(); // from migrations/ folder
async fn test_db() -> PgPool {
let pool = PgPoolOptions::new()
.max_connections(1)
.connect("postgres://localhost/test")
.await
.expect("test database");
MIGRATOR.run(&pool).await.expect("migrations");
pool
}
#[sqlx::test]
async fn test_create_user() {
let pool = test_db().await;
let repo = UsersRepo::new(pool);
let id = repo.create_in_tx(&mut pool.begin().await.unwrap(), "test@example.com").await.unwrap();
assert!(id.0.to_string().len() > 0);
}
}Use SQLx's built-in #[sqlx::test] for per-test database transactions that roll back automatically.
Connection Pool Sizing
| Database | Formula | Typical Max |
| PostgreSQL | 2 * (core_count + disk_count) + 1 | 20-50 per instance |
| MySQL | CPU cores * 2 + effective disk count | 50-200 |
| SQLite | 1 writer + N readers | WAL mode: 1 writer + many readers |
Monitor pool utilization. If connections are always maxed, you need more replicas or query optimization, not a bigger pool.
Anti-Patterns
// Bad: transaction hidden inside a helper, making multi-write atomicity impossible.
async fn create_user(pool: &PgPool, email: &str) { /* begins and commits internally */ }
// Good: functions can accept &mut Transaction when the caller needs atomic composition.
async fn create_user(tx: &mut Transaction<'_, Postgres>, email: &str) { /* participates */ }// Bad: N+1 queries in a loop.
for user in users {
load_posts(pool, user.id).await?;
}
// Good: batch by IDs and map results in memory.
load_posts_for_users(pool, &user_ids).await?;// Bad: string interpolation in SQL (SQL injection risk).
sqlx::query(&format!("select * from users where id = '{}'", user_id));
// Good: parameterized query.
sqlx::query!("select * from users where id = $1", user_id);// Bad: holding a transaction open while doing non-database work.
let mut tx = pool.begin().await?;
call_external_api().await?; // transaction held open during external call
do_db_work(&mut tx).await?;
tx.commit().await?;
// Good: only hold transactions for database operations.
let result = call_external_api().await?;
let mut tx = pool.begin().await?;
do_db_work(&mut tx, result).await?;
tx.commit().await?;Production Checklist
- Migrations run before application startup or as a controlled release step.
- Every multi-write business operation has an explicit transaction boundary.
- Pool size, acquire timeout, and database connection limits are documented.
- Slow queries have indexes justified by
EXPLAIN, not guesswork. - Constraint errors are mapped into stable domain errors.
- Integration tests run against the same database engine used in production.
- Connection strings and credentials are not hardcoded — use secrets management.
- Pool metrics are exposed (used connections, wait time, total).
- Long-running transactions are logged and investigated.
References
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

