sqlx-record/.claude/skills/sqlx-record.md

3.9 KiB

sqlx-record Skill

Expert guidance for using the sqlx-record Rust library.

Triggers

  • "create entity", "define entity", "entity struct"
  • "sqlx record", "sqlx-record"
  • "crud operations", "database entity"
  • "audit trail", "change tracking"
  • "lookup table", "lookup options"

Overview

sqlx-record provides derive macros for automatic CRUD operations and audit trails for SQL entities. It supports MySQL, PostgreSQL, and SQLite.

Quick Reference

Entity Definition

use sqlx_record::prelude::*;
use sqlx::FromRow;

#[derive(Entity, FromRow)]
#[table_name = "users"]
struct User {
    #[primary_key]
    id: Uuid,

    #[rename("user_name")]  // Maps to different DB column
    name: String,

    #[version]  // Auto-increment on updates
    version: u32,

    #[field_type("TEXT")]  // SQLx type hint
    bio: Option<String>,
}

CRUD Operations

// Insert
let user = User { id: new_uuid(), name: "Alice".into(), version: 0 };
user.insert(&pool).await?;

// Get
let user = User::get_by_id(&pool, &id).await?;
let users = User::get_by_ids(&pool, &ids).await?;

// Find with filters
let users = User::find(&pool, filters![("is_active", true)], None).await?;
let user = User::find_one(&pool, filters![("email", email)], None).await?;

// Find with ordering and pagination
let page = User::find_ordered_with_limit(
    &pool,
    filters![("role", "admin")],
    None,
    vec![("created_at", false)],  // DESC
    Some((0, 10))  // offset, limit
).await?;

// Count
let count = User::count(&pool, filters![("is_active", true)], None).await?;

// Update
User::update_by_id(&pool, &id, User::update_form().with_name("Bob")).await?;

Filter System

// Simple equality
filters![("field", value)]

// Multiple conditions (AND)
filters![("active", true), ("role", "admin")]

// OR conditions
filter_or![("status", "active"), ("status", "pending")]

// Operators
"age".gt(18)      // >
"age".ge(18)      // >=
"age".lt(65)      // <
"age".le(65)      // <=
"name".eq("Bob")  // =
"name".ne("Bob")  // !=

// Other filters
Filter::Like("name", "%alice%".into())
Filter::In("status", vec!["active".into(), "pending".into()])
Filter::IsNull("deleted_at")
Filter::IsNotNull("email")

Lookup Tables

// With database entity
lookup_table!(OrderStatus, "pending", "shipped", "delivered");
// Generates: struct OrderStatus, enum OrderStatusCode, constants

// Without database entity
lookup_options!(PaymentMethod, "credit-card", "paypal", "bank-transfer");
// Generates: enum PaymentMethodCode, struct PaymentMethod (constants only)

// Usage
let status = OrderStatus::PENDING;  // "pending"
let code = OrderStatusCode::try_from("pending")?;  // OrderStatusCode::Pending

Time-Ordered UUIDs

let id = new_uuid();  // Timestamp prefix for better indexing

Feature Flags

[dependencies]
sqlx-record = { version = "0.2", features = ["mysql", "derive"] }
# Database: "mysql", "postgres", or "sqlite" (pick one)
# Optional: "derive", "static-validation"

Advanced Updates (UpdateExpr)

use sqlx_record::prelude::UpdateExpr;

// Arithmetic: score = score + 10
User::update_by_id(&pool, &id,
    User::update_form().eval_score(UpdateExpr::Add(10.into()))
).await?;

// CASE/WHEN
User::update_by_id(&pool, &id,
    User::update_form().eval_tier(UpdateExpr::Case {
        branches: vec![("score".gt(100), "gold".into())],
        default: "bronze".into(),
    })
).await?;

// Raw SQL escape hatch
User::update_by_id(&pool, &id,
    User::update_form().raw("computed", "COALESCE(a, 0) + b")
).await?;

ConnProvider (Flexible Connections)

use sqlx_record::ConnProvider;

// Borrowed or owned pool connections
let conn = ConnProvider::Borrowed(&pool);
let users = User::find(&*conn, filters![], None).await?;

Database Differences

Feature MySQL PostgreSQL SQLite
Placeholder ? $1, $2 ?
Table quote ` " "
Index hints Supported N/A N/A