agentleFS
dawidpereiradawidpereira/rust-skills/skills/rust-database/SKILL.md

rust-database

Skill0 starsChanged 8 months agoMITView on GitHub

Rust database access with sqlx — compile-time checked queries, migrations, connection pooling, transactions, type mapping for domain newtypes, and database testing. Use when choosing between sqlx, diesel, and sea-orm, writing queries with compile-time safety, setting up migrations, configuring connection pools, mapping Rust newtypes to SQL types, scoping transactions, testing repository layers against real databases, or diagnosing pool timeouts and migration failures.

4 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

2 rules. 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 0

    Distinctive

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If charge_payment fails, the ?

    line 120
  2. Very specific:

    Run with sqlx migrate run.

    line 140

“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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Database Access

Core Question

How does this data move between Rust's type system and the database, preserving correctness at both boundaries?

Compile-time query checking catches schema drift before it reaches production. Newtype wrappers carry domain meaning through the SQL boundary. Transactions scope the unit of change. If your types lie about what the database holds, no amount of testing will save you.


Error → Design Question

SymptomAsk Instead
"no rows returned"Should this be Option or an error?
"type mismatch"Are your Rust types aligned with the schema?
"pool timed out"Is the pool sized for this workload?
"migration failed"Is the migration reversible?
"column not found"Are compile-time queries in sync with the schema?
Transaction deadlockIs the transaction scope too broad?

Quick Decisions

SituationReach ForWhy
New project choosing DB librarysqlxCompile-time safety, no ORM overhead, async-native
Need ORM-like featuressea-ormBuilt on sqlx, ActiveRecord-style, code-gen migrations
Legacy schema, complex joinsdieselStrong type-safe query builder, sync-first
Compile-time checked queriessqlx::query!() with DATABASE_URLSchema mismatches fail at compile time
Runtime dynamic queriessqlx::query() with .bind()Flexible WHERE clauses, user-driven filters
Mapping rows to structsquery_as!() or FromRow deriveType-safe row mapping without manual extraction
Newtype in DB columnImplement Type + Encode + DecodeDomain types pass through SQL boundary cleanly
Schema migrationssqlx-cli with reversible migrationsVersion-controlled, repeatable, rollback support
Connection pool sizingCPU cores × 2 + disk spindles (start 5–10)Avoid starving the pool or overwhelming the DB
Transactionspool.begin(), commit on successAuto-rollback on drop prevents partial writes
Nested transactionsSavepoints via Transaction::begin()Partial rollback without aborting outer transaction
Testing with real DBPer-test database or transaction rollbackIsolation without mocks, real query execution
N+1 query problemBatch with WHERE IN or JOINSingle round-trip instead of N
Read replicasSeparate pool, route reads explicitlyKeep write pool available, scale reads independently

sqlx Query Styles

Four styles serve different needs:

  • query!() — compile-time checked against a live database or cached sqlx-data.json. Use for all static queries. Catches typos and type mismatches before runtime.
  • query_as!() — same compile-time checking but maps results directly to a named struct. Cleaner than manual field extraction.
  • query() — runtime-only, no compile-time checking. Use when the query shape depends on user input or conditional logic.
  • QueryBuilder — builds dynamic queries programmatically. Use for bulk inserts, dynamic WHERE clauses, or conditional JOINs.

See references/sqlx.md for examples of each style.


The Newtype-to-SQL Bridge

Domain newtypes (UserId(Uuid), Email(String)) must cross the SQL boundary. Implement sqlx::Type, sqlx::Encode, and sqlx::Decode — or derive them with #[derive(sqlx::Type)] for simple wrappers.

The #[sqlx(transparent)] attribute delegates to the inner type, keeping the domain wrapper invisible to SQL:

#[derive(sqlx::Type)]
#[sqlx(transparent)]
pub struct UserId(Uuid);

See references/sqlx.md for manual trait implementations when you need custom mapping.


Transaction Scoping

Transactions auto-rollback when dropped without an explicit commit(). Pass &mut Transaction through functions to keep the transaction scope visible:

let mut tx = pool.begin().await?;
create_order(&mut tx, &order).await?;
charge_payment(&mut tx, &payment).await?;
tx.commit().await?;

If charge_payment fails, the ? propagates the error, tx is dropped, and everything rolls back.

See references/transactions.md for savepoints, deadlock prevention, and read-only transactions.


Usage Scenarios

Scenario 1: "I'm building a new web service with Postgres" → Add sqlx with runtime-tokio and postgres features. Use sqlx-cli for migrations. Configure PgPoolOptions with 5–10 connections. Use query!() for all static queries. See references/sqlx.md for Cargo.toml and pool setup.

Scenario 2: "I need to evolve my database schema" → Create reversible migrations with sqlx migrate add. Run with sqlx migrate run. Keep sqlx-data.json in version control for offline compile-time checking. See references/sqlx.md for migration commands.

Scenario 3: "I'm testing my repository layer" → Use #[sqlx::test] for automatic test database management, or wrap each test in a transaction that never commits. Seed data with fixture functions. See references/testing.md for both strategies.

Scenario 4: "My domain types need to map to database columns" → Derive sqlx::Type with #[sqlx(transparent)] for simple newtypes. Implement Type/Encode/Decode manually for enums or complex mappings. See references/sqlx.md for full examples.


Reference Files

FileRead When
references/sqlx.mdCargo.toml setup, query macros, FromRow, QueryBuilder, pooling, migrations, newtype mapping
references/transactions.mdTransaction lifecycle, passing through functions, savepoints, deadlock prevention
references/testing.mdPer-test databases, transaction rollback, fixtures, sqlx::test macro, CI setup

Cross-References

WhenCheck
Repository pattern, module layoutrust-architecture → Quick Decisions
Repository traits, aggregate boundariesrust-ddd → Quick Decisions
Pool async behavior, spawn and .awaitrust-async → Quick Decisions
Test isolation, test builder patternrust-tests → Quick Decisions
Newtype design, derive strategiesrust-types → Quick Decisions
DTO mapping, serde for API responsesrust-serde → Quick Decisions

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.