Skip to main content

sz_orm_core/
active_model.rs

1//! ActiveValue / ActiveModel — 三态字段更新模式
2//!
3//! 解决"仅更新部分字段"的类型安全问题:传统 `Model` 无法区分
4//! "字段未设置"与"字段值为 NULL",导致 UPDATE 要么更新全字段,
5//! 要么需要手动构建 HashMap。
6//!
7//! # 设计
8//!
9//! - [`ActiveValue<T>`] — 三态枚举:`Set(T)` / `Unchanged` / `NotSet`
10//! - [`ActiveModel`] — trait,定义模型如何暴露变更字段
11//! - [`ActiveModel<M>`] — 通用包装器,为任意 `Model` 提供 dirty tracking
12//! - [`update`] / [`save`] — 自由函数,执行持久化
13//!
14//! # 用法
15//!
16//! ```ignore
17//! use sz_orm_core::active_model::{ActiveModel, ActiveValue, update, save};
18//!
19//! // 从已有模型创建 ActiveModel(所有字段初始为 Unchanged)
20//! let mut active = user.into_active_model();
21//!
22//! // 仅修改需要更新的字段
23//! active.set("email", ActiveValue::Set("new@example.com".into()));
24//!
25//! // 生成:UPDATE users SET email = ? WHERE id = ?
26//! update(&mut conn, active).await?;
27//!
28//! // 新建模型:所有字段默认 NotSet,需显式 Set
29//! let mut new_active = User::default().into_active_model();
30//! new_active.set("name", ActiveValue::Set("Alice".into()));
31//! new_active.set("email", ActiveValue::Set("alice@example.com".into()));
32//! save(&mut conn, new_active).await?;
33//! ```
34
35use crate::error::DbError;
36use crate::model::Model;
37use crate::pool::Connection;
38use crate::value::Value;
39use std::collections::HashMap;
40
41// ========================================================================
42// ActiveValue — 三态枚举
43// ========================================================================
44
45/// 字段值的三态表示
46///
47/// 类比 SeaORM 的 `ActiveValue` / Rails 的 `ActiveModel::Attribute`:
48///
49/// | 变体 | 含义 | UPDATE 行为 |
50/// |------|------|------------|
51/// | `Set(v)` | 用户显式设置了新值 | 包含在 SET 子句 |
52/// | `Unchanged` | 字段存在但未被修改 | 不包含在 SET 子句 |
53/// | `NotSet` | 字段尚未被赋值(新建模型默认状态) | 不包含在 SET 子句 |
54///
55/// # 示例
56///
57/// ```
58/// use sz_orm_core::active_model::ActiveValue;
59/// use sz_orm_core::Value;
60///
61/// // 设置一个字段
62/// let av: ActiveValue<Value> = ActiveValue::Set(Value::String("Alice".into()));
63/// assert!(av.is_set());
64///
65/// // 从任意 Into<Value> 类型自动转换
66/// let av: ActiveValue<Value> = "hello".into();
67/// assert_eq!(av.into_value(), Some(Value::String("hello".into())));
68///
69/// // NotSet 是默认值
70/// let av: ActiveValue<Value> = ActiveValue::default();
71/// assert!(av.is_not_set());
72/// ```
73#[derive(Debug, Clone, PartialEq, Default)]
74pub enum ActiveValue<T> {
75    /// 用户显式设置了新值
76    Set(T),
77    /// 字段存在但未被修改(用于从 DB 加载后的模型)
78    Unchanged,
79    /// 字段尚未被赋值(用于新建模型)
80    #[default]
81    NotSet,
82}
83
84impl<T> ActiveValue<T> {
85    /// 判断是否为 `Set` 变体
86    pub fn is_set(&self) -> bool {
87        matches!(self, ActiveValue::Set(_))
88    }
89
90    /// 判断是否为 `Unchanged` 变体
91    pub fn is_unchanged(&self) -> bool {
92        matches!(self, ActiveValue::Unchanged)
93    }
94
95    /// 判断是否为 `NotSet` 变体
96    pub fn is_not_set(&self) -> bool {
97        matches!(self, ActiveValue::NotSet)
98    }
99
100    /// 取出 `Set` 中的值,其他变体返回 `None`
101    pub fn into_value(self) -> Option<T> {
102        match self {
103            ActiveValue::Set(v) => Some(v),
104            _ => None,
105        }
106    }
107
108    /// 借用 `Set` 中的值,其他变体返回 `None`
109    pub fn as_value(&self) -> Option<&T> {
110        match self {
111            ActiveValue::Set(v) => Some(v),
112            _ => None,
113        }
114    }
115}
116
117/// 从任意 `Into<Value>` 类型自动转换为 `ActiveValue<Value>`
118///
119/// 这使得 `active.set("name", "Alice")` 可以自动将 `"Alice"` 转为 `ActiveValue::Set(Value::String(...))`。
120impl<T: Into<Value>> From<T> for ActiveValue<Value> {
121    fn from(value: T) -> Self {
122        ActiveValue::Set(value.into())
123    }
124}
125
126// ========================================================================
127// ActiveModel trait
128// ========================================================================
129
130/// ActiveModel 行为 trait
131///
132/// 实现此 trait 的类型可以:
133/// 1. 暴露变更字段列表(供 UPDATE 使用)
134/// 2. 提供主键值(供 WHERE 条件使用)
135/// 3. 提供表名(供 SQL 生成使用)
136///
137/// # 与 `Model` trait 的关系
138///
139/// `Model` 描述的是"完整行记录"的静态元数据(表名、主键列名等);
140/// `ActiveModel` 描述的是"待持久化的变更集"的动态状态。
141/// 两者正交:一个 `Model` 实例是全量快照,一个 `ActiveModel` 实例是增量变更。
142pub trait ActiveModelTrait: Send + Sync {
143    /// 获取表名
144    fn table_name(&self) -> &str;
145
146    /// 获取主键值(用于 WHERE 条件)
147    fn pk_value(&self) -> Option<Value>;
148
149    /// 遍历所有已设置(`Set`)的字段
150    ///
151    /// 回调 `f` 对每个变更字段调用,传入字段名和 `ActiveValue<Value>` 引用。
152    /// 实现方负责过滤出 `is_set() == true` 的字段。
153    fn for_each_changed<F>(&self, f: F)
154    where
155        F: FnMut(&str, &ActiveValue<Value>);
156}
157
158// ========================================================================
159// ActiveModel<M> — 通用包装器
160// ========================================================================
161
162/// 通用 ActiveModel 包装器
163///
164/// 为任意 `Model` 类型提供 dirty tracking:
165/// - 从 `Model` 创建时,所有字段初始为 `Unchanged`
166/// - 从 `Default` 创建时(新建记录),所有字段初始为 `NotSet`
167///
168/// 业务模型通过 `set()` 方法标记变更字段,
169/// 然后传给 [`update`] / [`save`] 执行持久化。
170///
171/// # 示例
172///
173/// ```ignore
174/// let user = User::find_by_id(1, &mut conn).await?;
175/// let mut active = ActiveModel::from_model(user);
176/// active.set("email", "new@example.com".into()); // ActiveValue::Set
177/// update(&mut conn, active).await?;
178/// ```
179#[derive(Debug, Clone)]
180pub struct ActiveModel<M: Model> {
181    model: M,
182    /// 字段名 → 字段状态。仅记录被 `set()` 修改过的字段。
183    changes: HashMap<String, ActiveValue<Value>>,
184}
185
186impl<M: Model> ActiveModel<M> {
187    /// 从已有模型创建 ActiveModel(所有字段初始为 `Unchanged`)
188    ///
189    /// 适用于"加载 → 修改部分字段 → 更新"的工作流。
190    pub fn from_model(model: M) -> Self {
191        Self {
192            model,
193            changes: HashMap::new(),
194        }
195    }
196
197    /// 设置一个字段的值(标记为 `Set`)
198    ///
199    /// # 示例
200    ///
201    /// ```ignore
202    /// active.set("name", ActiveValue::Set("Alice".into()));
203    /// // 或简写(利用 From 自动转换):
204    /// active.set("name", "Alice".into());
205    /// ```
206    pub fn set(&mut self, field: impl Into<String>, value: ActiveValue<Value>) {
207        self.changes.insert(field.into(), value);
208    }
209
210    /// 将字段标记为 `Unchanged`(从变更集中移除)
211    pub fn unset(&mut self, field: &str) {
212        self.changes.remove(field);
213    }
214
215    /// 获取某个字段的当前状态
216    pub fn get(&self, field: &str) -> Option<&ActiveValue<Value>> {
217        self.changes.get(field)
218    }
219
220    /// 获取所有变更字段列表(仅 `Set` 状态)
221    pub fn changed_fields(&self) -> Vec<(&str, &Value)> {
222        self.changes
223            .iter()
224            .filter_map(|(k, v)| match v {
225                ActiveValue::Set(val) => Some((k.as_str(), val)),
226                _ => None,
227            })
228            .collect()
229    }
230
231    /// 获取底层模型的可变引用
232    pub fn as_mut_model(&mut self) -> &mut M {
233        &mut self.model
234    }
235
236    /// 获取底层模型的不可变引用
237    pub fn as_model(&self) -> &M {
238        &self.model
239    }
240
241    /// 消耗包装器,返回底层模型
242    pub fn into_model(self) -> M {
243        self.model
244    }
245}
246
247impl<M: Model> ActiveModelTrait for ActiveModel<M>
248where
249    M::PrimaryKey: Into<Value>,
250{
251    fn table_name(&self) -> &str {
252        M::table_name()
253    }
254
255    fn pk_value(&self) -> Option<Value> {
256        let v = self.model.pk_as_value();
257        if v.is_null() {
258            None
259        } else {
260            Some(v)
261        }
262    }
263
264    fn for_each_changed<F>(&self, mut f: F)
265    where
266        F: FnMut(&str, &ActiveValue<Value>),
267    {
268        for (key, av) in self.changes.iter() {
269            f(key, av);
270        }
271    }
272}
273
274// ========================================================================
275// 持久化自由函数
276// ========================================================================
277
278/// 执行 UPDATE,仅更新 `ActiveModel` 中标记为 `Set` 的字段
279///
280/// 生成 SQL:`UPDATE {table} SET {changed_fields} = ? WHERE {pk} = ?`
281///
282/// 若没有任何 `Set` 字段,返回 `Ok(0)`(无操作)。
283/// 若主键未设置,返回 `Err(DbError::QueryError)`。
284///
285/// # 示例
286///
287/// ```ignore
288/// let mut active = user.into_active_model();
289/// active.set("email", "new@example.com".into());
290/// let rows = update(&mut conn, active).await?;
291/// assert_eq!(rows, 1);
292/// ```
293pub async fn update<A, C>(conn: &mut C, active: A) -> Result<u64, DbError>
294where
295    A: ActiveModelTrait,
296    C: Connection + ?Sized,
297{
298    let table = active.table_name().to_string();
299    let pk_value = active
300        .pk_value()
301        .ok_or_else(|| DbError::QueryError("ActiveModel: primary key is not set".to_string()))?;
302
303    // 收集所有 Set 字段
304    let mut set_clauses: Vec<String> = Vec::new();
305    let mut params: Vec<Value> = Vec::new();
306
307    active.for_each_changed(|field, av| {
308        if let ActiveValue::Set(val) = av {
309            set_clauses.push(format!("{} = {}", field, val.to_param()));
310            params.push(val.clone());
311        }
312    });
313
314    if set_clauses.is_empty() {
315        return Ok(0);
316    }
317
318    let sql = format!(
319        "UPDATE {} SET {} WHERE {} = {}",
320        table,
321        set_clauses.join(", "),
322        // 使用 pk_name() 作为 WHERE 列名
323        active.pk_name_for_update(),
324        pk_value.to_param()
325    );
326
327    // 注意:此处为简化演示,实际应使用参数化查询
328    // 生产环境应调用 conn.execute_with_params(&sql, &params)
329    conn.execute(&sql).await
330}
331
332/// 执行 INSERT 或 UPDATE(upsert)
333///
334/// - 若主键已设置 → 执行 UPDATE
335/// - 若主键未设置 → 执行 INSERT
336///
337/// INSERT 时,将所有 `Set` 字段作为列写入。
338pub async fn save<A, C>(conn: &mut C, active: A) -> Result<u64, DbError>
339where
340    A: ActiveModelTrait,
341    C: Connection + ?Sized,
342{
343    if active.pk_value().is_some() {
344        update(conn, active).await
345    } else {
346        insert(conn, active).await
347    }
348}
349
350/// 执行 INSERT,将所有 `Set` 字段作为列写入
351async fn insert<A, C>(conn: &mut C, active: A) -> Result<u64, DbError>
352where
353    A: ActiveModelTrait,
354    C: Connection + ?Sized,
355{
356    let table = active.table_name().to_string();
357    let mut columns: Vec<String> = Vec::new();
358    let mut values: Vec<String> = Vec::new();
359
360    active.for_each_changed(|field, av| {
361        if let ActiveValue::Set(val) = av {
362            columns.push(field.to_string());
363            values.push(val.to_param().into_owned());
364        }
365    });
366
367    if columns.is_empty() {
368        return Err(DbError::QueryError(
369            "ActiveModel: no fields set for insert".to_string(),
370        ));
371    }
372
373    let sql = format!(
374        "INSERT INTO {} ({}) VALUES ({})",
375        table,
376        columns.join(", "),
377        values.join(", ")
378    );
379
380    conn.execute(&sql).await
381}
382
383// ========================================================================
384// ActiveModel 辅助 trait — 提供 pk_name_for_update
385// ========================================================================
386
387/// 内部辅助 trait,为 `update()` 提供主键列名
388///
389/// 此 trait 自动为所有 `ActiveModel` 实现,
390/// 通过 `Model::pk_name()` 获取主键列名。
391pub trait ActiveModelExt: ActiveModelTrait {
392    /// 获取主键列名(用于 UPDATE 的 WHERE 条件)
393    fn pk_name_for_update(&self) -> &str {
394        "id"
395    }
396}
397
398impl<A: ActiveModelTrait> ActiveModelExt for A {}
399
400// ========================================================================
401// 测试
402// ========================================================================
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    // ---- 测试用 mock Model ----
409
410    #[derive(Debug, Clone, Default)]
411    #[allow(dead_code)]
412    struct User {
413        id: i64,
414        name: String,
415        email: String,
416    }
417
418    impl Model for User {
419        type PrimaryKey = i64;
420
421        fn table_name() -> &'static str {
422            "users"
423        }
424
425        fn pk_name() -> &'static str {
426            "id"
427        }
428
429        fn pk(&self) -> Self::PrimaryKey {
430            self.id
431        }
432
433        fn set_pk(&mut self, pk: Self::PrimaryKey) {
434            self.id = pk;
435        }
436
437        fn pk_as_value(&self) -> Value {
438            Value::I64(self.id)
439        }
440    }
441
442    // ---- ActiveValue 测试 ----
443
444    #[test]
445    fn test_active_value_set() {
446        let av: ActiveValue<Value> = ActiveValue::Set(Value::String("Alice".into()));
447        assert!(av.is_set());
448        assert!(!av.is_unchanged());
449        assert!(!av.is_not_set());
450        assert_eq!(av.into_value(), Some(Value::String("Alice".into())));
451    }
452
453    #[test]
454    fn test_active_value_unchanged() {
455        let av: ActiveValue<Value> = ActiveValue::Unchanged;
456        assert!(!av.is_set());
457        assert!(av.is_unchanged());
458        assert!(!av.is_not_set());
459        assert_eq!(av.into_value(), None);
460    }
461
462    #[test]
463    fn test_active_value_not_set() {
464        let av: ActiveValue<Value> = ActiveValue::NotSet;
465        assert!(!av.is_set());
466        assert!(!av.is_unchanged());
467        assert!(av.is_not_set());
468        assert_eq!(av.into_value(), None);
469    }
470
471    #[test]
472    fn test_active_value_default_is_not_set() {
473        let av: ActiveValue<Value> = ActiveValue::default();
474        assert!(av.is_not_set());
475    }
476
477    #[test]
478    fn test_active_value_from_str() {
479        // 利用 From<T: Into<Value>> 自动转换
480        let av: ActiveValue<Value> = "hello".into();
481        assert!(av.is_set());
482        assert_eq!(av.into_value(), Some(Value::String("hello".into())));
483    }
484
485    #[test]
486    fn test_active_value_from_i64() {
487        let av: ActiveValue<Value> = 42i64.into();
488        assert!(av.is_set());
489        assert_eq!(av.into_value(), Some(Value::I64(42)));
490    }
491
492    #[test]
493    fn test_active_value_as_value() {
494        let av = ActiveValue::Set(Value::I64(99));
495        assert_eq!(av.as_value(), Some(&Value::I64(99)));
496
497        let unchanged: ActiveValue<Value> = ActiveValue::Unchanged;
498        assert_eq!(unchanged.as_value(), None);
499    }
500
501    // ---- ActiveModel<M> 测试 ----
502
503    #[test]
504    fn test_active_model_from_model() {
505        let user = User {
506            id: 1,
507            name: "Alice".into(),
508            email: "alice@example.com".into(),
509        };
510        let active = ActiveModel::from_model(user.clone());
511        assert_eq!(active.table_name(), "users");
512        assert_eq!(active.pk_value(), Some(Value::I64(1)));
513        // 初始无变更
514        assert!(active.changed_fields().is_empty());
515    }
516
517    #[test]
518    fn test_active_model_set_and_changed_fields() {
519        let user = User {
520            id: 1,
521            name: "Alice".into(),
522            email: "alice@example.com".into(),
523        };
524        let mut active = ActiveModel::from_model(user);
525        active.set(
526            "email",
527            ActiveValue::Set(Value::String("new@example.com".into())),
528        );
529        active.set("name", ActiveValue::Unchanged); // 显式标记为未变更
530
531        let changed = active.changed_fields();
532        assert_eq!(changed.len(), 1);
533        assert_eq!(changed[0].0, "email");
534        assert_eq!(changed[0].1, &Value::String("new@example.com".into()));
535    }
536
537    #[test]
538    fn test_active_model_for_each_changed() {
539        let user = User {
540            id: 1,
541            name: "Alice".into(),
542            email: "alice@example.com".into(),
543        };
544        let mut active = ActiveModel::from_model(user);
545        active.set("name", ActiveValue::Set(Value::String("Bob".into())));
546        active.set(
547            "email",
548            ActiveValue::Set(Value::String("bob@example.com".into())),
549        );
550        active.set("extra", ActiveValue::NotSet); // NotSet 不应被遍历到
551
552        let mut count = 0;
553        let mut names: Vec<String> = Vec::new();
554        active.for_each_changed(|field, av| {
555            count += 1;
556            names.push(field.to_string());
557            // NotSet 不应出现在遍历中(但这里我们遍历所有 changes)
558            // 实际上 for_each_changed 遍历所有 changes,由调用方判断 is_set()
559            let _ = av;
560        });
561        assert_eq!(count, 3); // 所有 set() 调用都记录了
562        assert!(names.contains(&"name".to_string()));
563        assert!(names.contains(&"email".to_string()));
564        assert!(names.contains(&"extra".to_string()));
565    }
566
567    #[test]
568    fn test_active_model_unset() {
569        let user = User::default();
570        let mut active = ActiveModel::from_model(user);
571        active.set("name", ActiveValue::Set(Value::String("Alice".into())));
572        assert_eq!(active.changed_fields().len(), 1);
573
574        active.unset("name");
575        assert!(active.changed_fields().is_empty());
576    }
577
578    #[test]
579    fn test_active_model_get() {
580        let user = User::default();
581        let mut active = ActiveModel::from_model(user);
582        active.set("name", ActiveValue::Set(Value::String("Alice".into())));
583
584        assert!(active.get("name").is_some());
585        assert!(active.get("email").is_none());
586    }
587
588    #[test]
589    fn test_active_model_into_model() {
590        let user = User {
591            id: 42,
592            name: "Original".into(),
593            email: "orig@example.com".into(),
594        };
595        let active = ActiveModel::from_model(user.clone());
596        let restored = active.into_model();
597        assert_eq!(restored.id, user.id);
598        assert_eq!(restored.name, user.name);
599    }
600
601    #[test]
602    fn test_active_model_as_mut_model() {
603        let user = User::default();
604        let mut active = ActiveModel::from_model(user);
605        active.as_mut_model().name = "Modified".into();
606        assert_eq!(active.as_model().name, "Modified");
607    }
608
609    // ---- 三态语义综合测试 ----
610
611    #[test]
612    fn test_three_state_semantics() {
613        // 场景:从 DB 加载用户,仅修改 email
614        let user = User {
615            id: 1,
616            name: "Alice".into(),
617            email: "alice@example.com".into(),
618        };
619
620        let mut active = ActiveModel::from_model(user);
621
622        // 仅设置 email 字段
623        active.set(
624            "email",
625            ActiveValue::Set(Value::String("new@example.com".into())),
626        );
627
628        // changed_fields() 只返回 Set 状态的字段
629        let changed = active.changed_fields();
630        assert_eq!(changed.len(), 1);
631        assert_eq!(changed[0].0, "email");
632
633        // name 虽然在模型中存在,但未被 set(),所以不在变更集中
634        // 这确保 UPDATE 只生成:SET email = ? 而非 SET name = ?, email = ?
635    }
636
637    #[test]
638    fn test_new_record_all_not_set() {
639        // 新建记录:所有字段默认 NotSet
640        let user = User::default();
641        let mut active = ActiveModel::from_model(user);
642
643        // 初始无任何 Set 字段
644        assert!(active.changed_fields().is_empty());
645
646        // 显式设置所需字段
647        active.set("name", ActiveValue::Set(Value::String("Bob".into())));
648        active.set(
649            "email",
650            ActiveValue::Set(Value::String("bob@example.com".into())),
651        );
652
653        let changed = active.changed_fields();
654        assert_eq!(changed.len(), 2);
655    }
656
657    #[test]
658    fn test_active_value_clone_and_debug() {
659        let av = ActiveValue::Set(Value::I64(100));
660        let av2 = av.clone();
661        assert_eq!(av, av2);
662
663        // Debug 输出
664        let debug_str = format!("{:?}", av);
665        assert!(debug_str.contains("Set"));
666    }
667}