# 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] ```rust #[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] ```rust #[primary_key] id: Uuid, ``` - Required on one field - Generates `get_by_{pk}`, `update_by_{pk}` methods - Supports: Uuid, String, i32, i64, etc. ### #[rename] ```rust #[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] ```rust #[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] ```rust #[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"` ### #[soft_delete] ```rust #[soft_delete] is_deleted: bool, ``` - Enables soft delete functionality - Generates `delete()`, `restore()`, `hard_delete()` methods - Field must be `bool` type ### #[created_at] ```rust #[created_at] created_at: i64, ``` - Auto-set to current timestamp (milliseconds) on insert - Field must be `i64` type - Excluded from UpdateForm ### #[updated_at] ```rust #[updated_at] updated_at: i64, ``` - Auto-set to current timestamp (milliseconds) on every update - Field must be `i64` type - Excluded from UpdateForm ## Generated Methods ### Insert ```rust pub async fn insert(&self, executor: E) -> Result // Batch insert pub async fn insert_many(executor, entities: &[Self]) -> Result, Error> // Insert or update on conflict pub async fn upsert(&self, executor) -> Result pub async fn insert_or_update(&self, executor) -> Result // alias ``` ### Get Methods ```rust // By single primary key pub async fn get_by_id(executor, id: &Uuid) -> Result, Error> // By multiple primary keys pub async fn get_by_ids(executor, ids: &[Uuid]) -> Result, Error> // Generic primary key access pub async fn get_by_primary_key(executor, pk: &PkType) -> Result, Error> ``` ### Find Methods ```rust // Basic find pub async fn find(executor, filters: Vec, index: Option<&str>) -> Result, Error> // Find first match pub async fn find_one(executor, filters: Vec, index: Option<&str>) -> Result, Error> // With ordering pub async fn find_ordered( executor, filters: Vec, index: Option<&str>, order_by: Vec<(&str, bool)> // (field, is_ascending) ) -> Result, Error> // With ordering and pagination pub async fn find_ordered_with_limit( executor, filters: Vec, index: Option<&str>, order_by: Vec<(&str, bool)>, offset_limit: Option<(u32, u32)> // (offset, limit) ) -> Result, Error> // Count matching pub async fn count(executor, filters: Vec, index: Option<&str>) -> Result // Paginated results pub async fn paginate( executor, filters: Vec, index: Option<&str>, order_by: Vec<(&str, bool)>, page_request: PageRequest ) -> Result, Error> // Select specific columns only pub async fn find_partial( executor, select_fields: &[&str], filters: Vec, index: Option<&str> ) -> Result, Error> ``` ### Update Methods ```rust // 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 ```rust // 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 // 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) ```rust pub async fn get_version(executor, pk: &PkType) -> Result, Error> pub async fn get_versions(executor, pks: &[PkType]) -> Result, Error> ``` ### Soft Delete Methods (if #[soft_delete] exists) ```rust // Soft delete - sets field to true pub async fn delete(&self, executor) -> Result<(), Error> pub async fn delete_by_id(executor, id: &Uuid) -> Result<(), Error> // Hard delete - permanently removes row pub async fn hard_delete(&self, executor) -> Result<(), Error> pub async fn hard_delete_by_id(executor, id: &Uuid) -> Result<(), Error> // Restore - sets field to false pub async fn restore(&self, executor) -> Result<(), Error> pub async fn restore_by_id(executor, id: &Uuid) -> Result<(), Error> // Get field name pub const fn soft_delete_field() -> &'static str ``` ### Metadata Methods ```rust 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. ```rust // 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 ```rust 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, } // 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(()) } ```