模型
#[derive(Model)] 会为具名字段结构体生成表元数据、行解码、值转换和类型化字段代理。使用
Repository 前必须注册模型;注册本身不会创建或迁移数据库表。
use afastorm::Model;
#[derive(Debug, Model)]
#[orm(table = "users")]
struct User {
#[orm(primary_key, auto_increment)]
id: Option<i64>,
#[orm(unique)]
email: String,
display_name: String,
metadata: serde_json::Value,
#[orm(soft_delete)]
deleted_at: Option<String>,
#[orm(version)]
version: i64,
}
默认表名是结构体名称转为小写蛇形命名,不自动复数化,例如 BlogPost 对应 blog_post。
#[orm(table = "...")] 和 #[orm(column = "...")] 可分别覆盖表名和字段对应的列名。每个模型
最多声明一个主键、软删除字段和版本字段;auto_increment 必须与主键一起使用。
生成式 find_by 方法
字段上的 #[orm(find_by)] 会生成单字段查找;模型上的 find_by(...) 会生成组合查找,多个值
使用 AND 组合:
#[derive(Debug, Model)]
#[orm(find_by(tenant_id, username))]
struct User {
#[orm(primary_key, auto_increment)]
id: Option<i64>,
#[orm(find_by)]
email: String,
tenant_id: i64,
username: String,
}
上述声明会生成 find_by_email 和 find_by_tenant_id_username。模型级属性使用 Rust 字段名,
不是数据库列名;即使字段使用 #[orm(column = "...")] 重命名,查找属性仍写 Rust 字段名。
重复的方法名和未知字段会导致编译失败。可以重复声明多个模型级 find_by(...)。
方法由模型专属扩展 trait 提供,调用处需要导入该 trait:
use crate::models::{User, UserFindBy};
let user = orm.repo::<User>()?
.find_by_tenant_id_username(7, "ada".to_owned())
.await?;
返回类型为 Result<Option<User>>。方法只取一条匹配记录,不保证顺序,也不要求字段具有唯一约束。
对于 Option<T> 字段,参数类型是 T;要查询 SQL NULL,使用
first_where(User::field.is_null())。
生成式 JSON 查询
通过可重复的模型级 find_json 声明固定 JSON 路径及可用操作:
#[derive(Debug, Model)]
#[orm(
find_json(profile.settings.theme, eq, ne, string_match),
find_json(profile.settings.age, numeric_compare),
find_json(profile.settings, contains, has_key, has_any_keys, has_all_keys)
)]
struct User {
#[orm(primary_key, auto_increment)]
id: Option<i64>,
profile: serde_json::Value,
}
路径第一段必须是 serde_json::Value 或 Option<serde_json::Value> 模型字段,之后的段落是
JSON 对象键,且必须为合法 Rust 标识符。方法名采用操作在前的形式,例如
find_json_eq_profile_settings_theme。未知字段、非 JSON 字段、无效路径和重复方法名会在
derive 阶段报错。宏检查的是 Rust 模型声明,不会在编译时读取外部数据库结构。
生成方法由 UserFindJson trait 提供。所有方法返回 Result<Option<User>>,查找第一条结果且
不保证顺序,并自动应用软删除过滤。
| 声明的操作 | 生成的方法 | 参数类型 | 含义 |
|---|---|---|---|
eq、ne | eq、ne | serde_json::Value | JSON 值相等或不等;对象键顺序无关,数组顺序有关。 |
contains | contains | serde_json::Value | 递归包含;对象键值子集和无序数组元素子集可匹配。 |
has_key | has_key | String | 路径指向的对象包含指定键。 |
has_any_keys、has_all_keys | 同名方法 | Vec<String> | 路径指向的对象包含任意或全部指定键。 |
numeric_compare | gt、gte、lt、lte | serde_json::Number | 数值比较;缺失路径或非数字不匹配。 |
string_match | like、starts_with、ends_with、string_contains | String | SQL 模式或字面字符串匹配。 |
相等和包含的值可用 json!(...) 构造,包括 JSON null。JSON null 与 SQL NULL 不同;
SQL 空值或不存在的路径不会匹配这些查找。like 中 %、_ 是 SQL 通配符,其余字符串方法
按区分大小写的字面值匹配。SQLite 使用 JSON1;PostgreSQL 默认将 JSON 字段映射为 JSONB。
自定义 Dialect 若未实现 render_json_lookup,会明确返回不支持错误。
支持的模型字段类型
以下类型及其 Option<T> 形式可用于 derive:
- 始终可用:
bool、i16、i32、i64、f32、f64、String、Vec<u8>、serde_json::Value。 uuid:uuid::Uuid。chrono:NaiveDate、NaiveTime、NaiveDateTime、DateTime<Utc>和DateTime<FixedOffset>。time:Date、Time、PrimitiveDateTime和OffsetDateTime。rust_decimal:rust_decimal::Decimal;bigdecimal:bigdecimal::BigDecimal。ipnetwork:IpAddr、ipnetwork::IpNetwork;ipnet:IpAddr、ipnet::IpNet。mac_address:mac_address::MacAddress;bit-vec:bit_vec::BitVec。
sqlx::FromRow 对模型不是必需的,afastorm 会自行解码派生模型;直接使用 SQLx 查询结果结构体
时仍可使用它。
自定义类型
任意 newtype、具有不同 Rust 类型名的类型别名和自定义 SQLx 类型目前不能直接作为 derive 模型
字段。仅实现 IntoValue、SQLx 的 Type/Encode/Decode 或 PgArrayElement 并不会注册模型
字段类型及 schema 映射。PostgreSQL 用户自定义 enum 和 composite 字段也不受支持。
自定义查询结果可在所选驱动支持解码时通过 Orm::query_as 和 SQLx FromRow 使用。持久化自定义
字段可考虑在应用边界与 String 等内置类型相互转换,或手动实现 Model;当前没有 derive codec
属性。
启用 postgres 后,模型可使用 PgArray<T> 和 PgRange<T>;支持元素、范围和限制见
PostgreSQL 类型。它们不能用作 SQLite 模型字段。
软删除、自动时间戳和版本锁
#[orm(soft_delete)] 要求字段为 Option<T>。普通 Repository 读取和计数会过滤非空删除标记;
delete 设置标记,restore 清除标记,force_delete 执行物理删除。
软删除和自动时间戳支持 bool、i32、i64、f32、f64、String、
serde_json::Value、日期时间及 decimal 类型;不支持 i16、UUID、字节、网络、MAC、bit-vector、
数组和范围。数据库生成值的形式因后端和类型而异,选择字段类型前请查阅后端文档。
#[orm(auto_create)] 在插入时填充值;#[orm(auto_update)] 在插入和每次 Repository 更新时填值。
它们不能与主键、自增、软删除或版本属性组合。
#[orm(version)] 要求非空 i32 或 i64。插入时初始化为 1,成功的 Repository 更新、删除和
恢复都会递增版本。save/update 默认使用模型当前版本;过期写入返回乐观锁错误。
注册模型
let registry = ModelRegistry::new()
.register::<User>()
.register::<Post>();
let orm = orm.with_models(registry);
let users = orm.repo::<User>()?;
注册表为 Repository、schema 快照和集成层提供元数据。数据库表结构由迁移负责创建和演进。