239 lines
5.7 KiB
Markdown
239 lines
5.7 KiB
Markdown
# 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"`
|
|
|
|
## Generated Methods
|
|
|
|
### Insert
|
|
```rust
|
|
pub async fn insert<E>(&self, executor: E) -> Result<PkType, sqlx::Error>
|
|
```
|
|
|
|
### Get Methods
|
|
```rust
|
|
// 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
|
|
```rust
|
|
// 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
|
|
```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<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)
|
|
```rust
|
|
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
|
|
```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<T>.
|
|
|
|
```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<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(())
|
|
}
|
|
```
|