5.7 KiB
5.7 KiB
sqlx-record Entity Skill
Detailed guidance for #[derive(Entity)] macro.
Triggers
- "derive entity", "entity macro"
- "generate crud", "crud methods"
- "primary key", "version field"
- "table name", "rename field"
Struct Attributes
#[table_name]
#[derive(Entity, FromRow)]
#[table_name = "users"] // or #[table_name("users")]
struct User { ... }
- Optional: defaults to snake_case of struct name
User->users,OrderItem->order_items
Field Attributes
#[primary_key]
#[primary_key]
id: Uuid,
- Required on one field
- Generates
get_by_{pk},update_by_{pk}methods - Supports: Uuid, String, i32, i64, etc.
#[rename]
#[rename("user_name")] // or #[rename = "user_name"]
name: String,
- Maps struct field to different database column
- Use when DB column doesn't match Rust naming
#[version]
#[version]
version: u32,
- Auto-increments on every update
- Wraps on overflow (u32::MAX -> 0)
- Generates
get_version(),get_versions()methods - Supports: u32, u64, i32, i64
#[field_type]
#[field_type("BIGINT")] // or #[field_type = "BIGINT"]
large_count: i64,
- SQLx type hint for compile-time validation
- Adds type annotation in SELECT:
field as "field: TYPE"
Generated Methods
Insert
pub async fn insert<E>(&self, executor: E) -> Result<PkType, sqlx::Error>
Get Methods
// By single primary key
pub async fn get_by_id(executor, id: &Uuid) -> Result<Option<Self>, Error>
// By multiple primary keys
pub async fn get_by_ids(executor, ids: &[Uuid]) -> Result<Vec<Self>, Error>
// Generic primary key access
pub async fn get_by_primary_key(executor, pk: &PkType) -> Result<Option<Self>, Error>
Find Methods
// Basic find
pub async fn find(executor, filters: Vec<Filter>, index: Option<&str>) -> Result<Vec<Self>, Error>
// Find first match
pub async fn find_one(executor, filters: Vec<Filter>, index: Option<&str>) -> Result<Option<Self>, Error>
// With ordering
pub async fn find_ordered(
executor,
filters: Vec<Filter>,
index: Option<&str>,
order_by: Vec<(&str, bool)> // (field, is_ascending)
) -> Result<Vec<Self>, Error>
// With ordering and pagination
pub async fn find_ordered_with_limit(
executor,
filters: Vec<Filter>,
index: Option<&str>,
order_by: Vec<(&str, bool)>,
offset_limit: Option<(u32, u32)> // (offset, limit)
) -> Result<Vec<Self>, Error>
// Count matching
pub async fn count(executor, filters: Vec<Filter>, index: Option<&str>) -> Result<u64, Error>
Update Methods
// Update instance
pub async fn update(&self, executor, form: UpdateForm) -> Result<(), Error>
// Update by primary key
pub async fn update_by_id(executor, id: &Uuid, form: UpdateForm) -> Result<(), Error>
// Update multiple
pub async fn update_by_ids(executor, ids: &[Uuid], form: UpdateForm) -> Result<(), Error>
// Create update form
pub fn update_form() -> UpdateForm
Diff Methods
// Compare form with model
pub fn model_diff(form: &UpdateForm, model: &Self) -> serde_json::Value
// Compare form with database
pub async fn db_diff(form: &UpdateForm, pk: &PkType, executor) -> Result<Value, Error>
// Modify form to only include changes
pub fn diff_modify(form: &mut UpdateForm, model: &Self) -> serde_json::Value
// Convert entity to update form
pub fn to_update_form(&self) -> UpdateForm
// Get initial state as JSON
pub fn initial_diff(&self) -> serde_json::Value
Version Methods (if #[version] exists)
pub async fn get_version(executor, pk: &PkType) -> Result<Option<VersionType>, Error>
pub async fn get_versions(executor, pks: &[PkType]) -> Result<HashMap<PkType, VersionType>, Error>
Metadata Methods
pub const fn table_name() -> &'static str
pub fn entity_key(pk: &PkType) -> String // "/entities/{table}/{pk}"
pub fn entity_changes_table_name() -> String // "entity_changes_{table}"
pub const fn primary_key_field() -> &'static str
pub const fn primary_key_db_field() -> &'static str
pub fn primary_key(&self) -> &PkType
pub fn select_fields() -> Vec<&'static str>
UpdateForm
Generated struct {Entity}UpdateForm with all non-PK fields as Option.
// Builder pattern
let form = User::update_form()
.with_name("Alice")
.with_email("alice@example.com");
// Setter pattern
let mut form = User::update_form();
form.set_name("Alice");
// Execute
User::update_by_id(&pool, &id, form).await?;
Only set fields are updated - others remain unchanged.
Complete Example
use sqlx_record::prelude::*;
use sqlx::FromRow;
use uuid::Uuid;
#[derive(Entity, FromRow, Debug, Clone)]
#[table_name = "products"]
pub struct Product {
#[primary_key]
pub id: Uuid,
#[rename("product_name")]
pub name: String,
pub price_cents: i64,
pub category_id: Uuid,
pub is_active: bool,
#[version]
pub version: u32,
#[field_type("TEXT")]
pub description: Option<String>,
}
// Usage
async fn example(pool: &Pool) -> Result<(), Error> {
// Create
let product = Product {
id: new_uuid(),
name: "Widget".into(),
price_cents: 999,
category_id: category_id,
is_active: true,
version: 0,
description: Some("A great widget".into()),
};
product.insert(pool).await?;
// Read
let product = Product::get_by_id(pool, &product.id).await?.unwrap();
// Update
Product::update_by_id(pool, &product.id,
Product::update_form()
.with_price_cents(1299)
.with_is_active(false)
).await?;
// Find active products in category
let products = Product::find(pool,
filters![("is_active", true), ("category_id", category_id)],
None
).await?;
Ok(())
}