Sqlx Code Review
by @anderskev
Reviews sqlx database code for compile-time query checking, connection pool management, migration patterns, and PostgreSQL-specific usage. Use when reviewing...
clawhub install sqlx-code-reviewπ About This Skill
name: sqlx-code-review description: Reviews sqlx database code for compile-time query checking, connection pool management, migration patterns, and PostgreSQL-specific usage. Use when reviewing Rust code that uses sqlx, database queries, connection pools, or migrations. Covers offline mode, type mapping, and transaction patterns.
sqlx Code Review
Review Workflow
1. Check Cargo.toml β Note sqlx features (runtime-tokio, tls-rustls/tls-native-tls, postgres/mysql/sqlite, uuid, chrono, json, migrate) and Rust edition (2024 changes RPIT lifetime capture and removes need for async-trait)
2. Check query patterns β Compile-time checked (query!, query_as!) vs runtime (query, query_as)
3. Check pool configuration β Connection limits, timeouts, idle settings
4. Check migrations β File naming, reversibility, data migration safety
5. Check type mappings β Rust types align with SQL column types
Gates (evidence before severity)
Complete in order; do not assign Critical / Major until the gate for that claim is passed.
1. Scope β Identify the crate under review (Cargo.toml path) and the .rs files (or directory) you opened. Pass: At least one concrete path you inspected is named.
2. sqlx / compile claims β Before asserting issues about query! / query_as!, offline mode, sqlx.toml, DATABASE_URL, or Cargo features: open the relevant Cargo.toml and, if applicable, sqlx.toml or documented env. Pass: The finding cites a line or you state that those files were absent / out of scope.
3. Finding anchors β Each reported issue includes [FILE:LINE] per Output Format. Pass: No Critical or Major without a line reference.
4. Protocol β Load and complete beagle-rust:review-verification-protocol after gates 1β3 and before final severity labels. Pass: Protocol steps satisfied for each retained finding.
Output Format
Report findings as:
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
Quick Reference
| Issue Type | Reference | |------------|-----------| | Query macros, bind parameters, result mapping | references/queries.md | | Migrations, pool config, transaction patterns | references/migrations.md |
Review Checklist
Query Patterns
query!, query_as!) used where possiblesqlx.toml or DATABASE_URL configured for offline compile-time checking$1, $2)query_as! maps to named structs, not anonymous records, for public APIs.fetch_one(), .fetch_optional(), .fetch_all() chosen appropriately.fetch() (streaming) used for large result setsConnection Pool
PgPool shared via Arc or framework state (not created per-request)std::sync::LazyLock (not once_cell::sync::Lazy or lazy_static!) for static pool singletonsTransactions
pool.begin() used for multi-statement operationstx.begin()) if neededType Mapping
sqlx::Type derives match database column typesUuid, DateTime, Decimal types used (not strings for structured data)Option used for nullable columnsserde_json::Value used for JSONB columnsgen β reserved keyword in edition 2024 (use r#gen with #[sqlx(rename = "gen")] or choose a different name)Edition 2024 Compatibility
-> impl Stream or -> impl Future account for RPIT lifetime capture changes (all in-scope lifetimes captured by default; use + use<'a> for precise control)FromRow or Type trait impls use native async fn in traits where applicable (no #[async_trait] needed, stable since Rust 1.75)#[expect(unused)] over #[allow(unused)] for compile-time query fields only used in some code paths (self-cleaning lint suppression, stable since 1.81)std::sync::LazyLock (not once_cell or lazy_static!)Migrations
YYYYMMDDHHMMSS_description.sql)sqlx::migrate!() called at application startupSeverity Calibration
Critical
Major
query()) where compile-time (query!()) could verify correctness.fetch_all() on potentially large tablesgen without r#gen escape (edition 2024 compile failure)Minor
.fetch_optional() (using .fetch_one() then handling error for "not found")SELECT * when only specific columns neededonce_cell::sync::Lazy or lazy_static! used where std::sync::LazyLock works#[allow(unused)] instead of #[expect(unused)] for query fields (prefer self-cleaning lint suppression)Informational
query_as! for type-safe result mappingValid Patterns (Do NOT Flag)
query() for dynamic queries β Compile-time checking doesn't work with dynamic SQLsqlx::FromRow derive β Valid alternative to query_as! for reusable row typesTEXT columns for enum storage β Valid with sqlx::Type derive, simpler than custom SQL types.execute() ignoring row count β Acceptable for idempotent operations (upserts, deletes)r#gen with #[sqlx(rename = "gen")] β Correct edition 2024 workaround for gen columns in database types+ use<'a> on query helper return types β Precise RPIT lifetime capture (edition 2024)std::sync::LazyLock for static pool initialization β Replaces once_cell/lazy_static (stable since Rust 1.80)async fn in custom FromRow/Type trait impls β async-trait crate no longer needed (stable since Rust 1.75)Before Submitting Findings
Complete Gates (evidence before severity), then load and follow beagle-rust:review-verification-protocol before reporting any issue.