189 lines
6.0 KiB
Markdown
189 lines
6.0 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code when working with this repository.
|
|
|
|
## Project Overview
|
|
|
|
`sqlx-record` is a Rust library that provides derive macros for automatic CRUD operations and comprehensive audit trails for SQL entities. It supports MySQL, PostgreSQL, and SQLite via SQLx, tracking who changed what, when, and why with actor, session, and change set metadata.
|
|
|
|
**Repository:** https://git.awesomike.com/pub/sqlx-record.git
|
|
|
|
## Architecture
|
|
|
|
### Workspace Structure
|
|
```
|
|
sqlx-record/
|
|
├── src/ # Core library
|
|
│ ├── lib.rs # Public API exports and prelude
|
|
│ ├── models.rs # EntityChange struct, Action enum
|
|
│ ├── repositories.rs # Database query functions for entity changes
|
|
│ ├── value.rs # Type-safe Value enum, bind functions
|
|
│ ├── filter.rs # Filter enum for query conditions
|
|
│ └── helpers.rs # Utility macros
|
|
├── sqlx-record-derive/ # Procedural macro crate
|
|
│ └── src/
|
|
│ ├── lib.rs # #[derive(Entity, Update)] implementation
|
|
│ └── string_utils.rs # Pluralization helpers
|
|
├── sqlx-record-ctl/ # CLI tool for audit table management
|
|
│ └── src/main.rs
|
|
└── Cargo.toml # Workspace root
|
|
```
|
|
|
|
### Feature Flags
|
|
- `derive`: Enables `#[derive(Entity, Update)]` procedural macros
|
|
- `static-validation`: Enables compile-time SQLx query validation
|
|
- `mysql`: MySQL database support
|
|
- `postgres`: PostgreSQL database support
|
|
- `sqlite`: SQLite database support
|
|
|
|
**Note:** You must enable at least one database feature.
|
|
|
|
## Development Commands
|
|
|
|
```bash
|
|
# Build with MySQL
|
|
cargo build --features mysql
|
|
|
|
# Build with derive macros
|
|
cargo build --features "mysql,derive"
|
|
|
|
# Build CLI tool
|
|
cargo build -p sqlx-record-ctl --features mysql
|
|
|
|
# Test
|
|
cargo test --features mysql
|
|
|
|
# Release tag
|
|
make tag
|
|
```
|
|
|
|
## Derive Macro API
|
|
|
|
### Entity Attributes
|
|
```rust
|
|
#[derive(Entity, FromRow)]
|
|
#[table_name = "users"] // or #[table_name("users")]
|
|
struct User {
|
|
#[primary_key] // Mark primary key field
|
|
id: Uuid,
|
|
|
|
#[rename("user_name")] // Map to different DB column
|
|
name: String,
|
|
|
|
#[version] // Auto-increment on update
|
|
version: u32,
|
|
|
|
#[field_type("BIGINT")] // SQLx type hint
|
|
count: i64,
|
|
}
|
|
```
|
|
|
|
### Generated Methods
|
|
|
|
**Insert:**
|
|
- `insert(&pool) -> Result<PkType, Error>`
|
|
|
|
**Get:**
|
|
- `get_by_{pk}(&pool, &pk) -> Result<Option<Self>, Error>`
|
|
- `get_by_{pks}(&pool, &[pk]) -> Result<Vec<Self>, Error>`
|
|
- `get_by_primary_key(&pool, &pk) -> Result<Option<Self>, Error>`
|
|
|
|
**Find:**
|
|
- `find(&pool, filters, index) -> Result<Vec<Self>, Error>`
|
|
- `find_one(&pool, filters, index) -> Result<Option<Self>, Error>`
|
|
- `find_ordered(&pool, filters, index, order_by) -> Result<Vec<Self>, Error>`
|
|
- `find_ordered_with_limit(&pool, filters, index, order_by, offset_limit) -> Result<Vec<Self>, Error>`
|
|
- `count(&pool, filters, index) -> Result<u64, Error>`
|
|
|
|
**Update:**
|
|
- `update(&self, &pool, form) -> Result<(), Error>`
|
|
- `update_by_{pk}(&pool, &pk, form) -> Result<(), Error>`
|
|
- `update_by_{pks}(&pool, &[pk], form) -> Result<(), Error>`
|
|
- `update_form() -> UpdateForm` - Creates builder
|
|
|
|
**Diff:**
|
|
- `model_diff(&form, &model) -> serde_json::Value`
|
|
- `db_diff(&form, &pk, &pool) -> Result<serde_json::Value, Error>`
|
|
- `diff_modify(&mut form, &model) -> serde_json::Value`
|
|
- `to_update_form(&self) -> UpdateForm`
|
|
- `initial_diff(&self) -> serde_json::Value`
|
|
|
|
**Metadata:**
|
|
- `table_name() -> &'static str`
|
|
- `entity_key(&pk) -> String`
|
|
- `entity_changes_table_name() -> String`
|
|
- `primary_key_field() -> &'static str`
|
|
- `primary_key_db_field() -> &'static str`
|
|
- `primary_key(&self) -> &PkType`
|
|
- `select_fields() -> Vec<&'static str>`
|
|
|
|
**Version (if #[version] field exists):**
|
|
- `get_version(&pool, &pk) -> Result<Option<VersionType>, Error>`
|
|
- `get_versions(&pool, &[pk]) -> Result<HashMap<PkType, VersionType>, Error>`
|
|
|
|
## Filter API
|
|
|
|
```rust
|
|
use sqlx_record::prelude::*;
|
|
|
|
// Simple filters
|
|
let f = filters![("active", true), ("role", "admin")];
|
|
|
|
// Compound filters
|
|
let f = filter_or![("status", "active"), ("status", "pending")];
|
|
let f = filter_and![("age", 18), ("verified", true)];
|
|
|
|
// Filter enum variants
|
|
Filter::Equal("field", value)
|
|
Filter::NotEqual("field", value)
|
|
Filter::GreaterThan("field", value)
|
|
Filter::LessThan("field", value)
|
|
Filter::Like("field", pattern)
|
|
Filter::ILike("field", pattern) // Case-insensitive
|
|
Filter::In("field", vec![values])
|
|
Filter::NotIn("field", vec![values])
|
|
Filter::IsNull("field")
|
|
Filter::IsNotNull("field")
|
|
Filter::And(vec![filters])
|
|
Filter::Or(vec![filters])
|
|
```
|
|
|
|
## Database Differences
|
|
|
|
| Feature | MySQL | PostgreSQL | SQLite |
|
|
|---------|-------|------------|--------|
|
|
| Placeholder | `?` | `$1, $2, ...` | `?` |
|
|
| Table quote | `` ` `` | `"` | `"` |
|
|
| UUID type | `BINARY(16)` | `UUID` | `BLOB` |
|
|
| JSON type | `JSON` | `JSONB` | `TEXT` |
|
|
| ILIKE | `LOWER() LIKE LOWER()` | Native | `LOWER() LIKE LOWER()` |
|
|
| Index hints | `USE INDEX()` | N/A | N/A |
|
|
|
|
## Value Types
|
|
|
|
The `Value` enum supports:
|
|
- Integers: `Int8`, `Uint8`, `Int16`, `Uint16`, `Int32`, `Uint32`, `Int64`, `Uint64`
|
|
- `String`, `Bool`, `VecU8`
|
|
- `Uuid`
|
|
- `NaiveDate`, `NaiveDateTime`
|
|
|
|
## Entity Changes (Audit Trail)
|
|
|
|
The `EntityChange` struct tracks:
|
|
- `id`: Change record UUID
|
|
- `entity_id`: Target entity UUID
|
|
- `action`: insert/update/delete/restore/hard-delete
|
|
- `changed_at`: Timestamp (milliseconds)
|
|
- `actor_id`: Who made the change
|
|
- `session_id`: Session context
|
|
- `change_set_id`: Transaction grouping
|
|
- `new_value`: JSON payload of changes
|
|
|
|
## Important Notes
|
|
|
|
- Always enable a database feature (`mysql`, `postgres`, or `sqlite`)
|
|
- The `prelude` module exports commonly used items including `placeholder()` function
|
|
- Query filters use `Filter::build_where_clause()` internally
|
|
- Version fields auto-increment with overflow wrapping
|
|
- The CLI tool requires a `entity_changes_metadata` table with `table_name` and `is_auditable` columns
|