Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

模型

#[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、neeq、neserde_json::ValueJSON 值相等或不等;对象键顺序无关,数组顺序有关。
containscontainsserde_json::Value递归包含;对象键值子集和无序数组元素子集可匹配。
has_keyhas_keyString路径指向的对象包含指定键。
has_any_keys、has_all_keys同名方法Vec<String>路径指向的对象包含任意或全部指定键。
numeric_comparegt、gte、lt、lteserde_json::Number数值比较;缺失路径或非数字不匹配。
string_matchlike、starts_with、ends_with、string_containsStringSQL 模式或字面字符串匹配。

相等和包含的值可用 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 快照和集成层提供元数据。数据库表结构由迁移负责创建和演进。