Skip to main content

turso_orm/entity/
active_model.rs

1//! Active models, the mutable change-tracking form of a model, modeled by [`ActiveModelTrait`].
2//!
3//! A model is a snapshot; an active model is the same set of attributes with
4//! each one wrapped in an [`ActiveValue`] that records whether it should be
5//! written. That distinction is what lets `INSERT` send only the attributes
6//! the caller set (so database defaults apply to the rest), lets `UPDATE`
7//! touch only the attributes that changed, and lets [`ActiveModelTrait::save`]
8//! decide between insert and update by looking at the primary key alone.
9//!
10//! This module owns the attribute state machine, the write entry points
11//! (`insert`, `update`, `save`, `delete`) and the [`ActiveModelBehavior`]
12//! hooks around them. It does not render SQL — the `crate::query` builders
13//! do — and [`ActiveModelTrait::set`] returns a `Result` instead of
14//! panicking so that a value of the wrong type surfaces as [`DbErr::Type`].
15//!
16//! Three conversions complete the picture. [`IntoActiveModel`] turns a
17//! model, or a struct derived with `DeriveIntoActiveModel`, into an active
18//! model; [`IntoActiveValue`] is the per-attribute rule that derive applies,
19//! where an `Option` field means "set only when `Some`"; and
20//! [`TryIntoModel`] goes back from an active model whose every attribute
21//! carries a value to the plain model. Behind the `with-json` feature,
22//! [`ActiveModelTrait::set_from_json`] fills attributes from a JSON object
23//! keyed by column name, which is what a request body usually is.
24//!
25//! `decode_field` is exposed through the crate's `__private` module for
26//! generated code only.
27
28use async_trait::async_trait;
29use turso_orm_driver::ConnectionTrait;
30use turso_sql::Value;
31
32use super::base_entity::EntityTrait;
33use crate::query::{DeleteResult, Insert, UpdateOne};
34use crate::{DbErr, Result};
35
36/// The state of one attribute of an active model.
37#[derive(Clone, Debug, PartialEq, Eq, Default)]
38pub enum ActiveValue<T> {
39    /// A new value that will be written on the next insert or update.
40    Set(T),
41    /// The value currently stored; it is read back but never written.
42    Unchanged(T),
43    /// No value; the database default applies on insert.
44    #[default]
45    NotSet,
46}
47
48/// Builds an [`ActiveValue::Set`].
49///
50/// Named in `UpperCamelCase` so that `Set(v)` reads like the variant it
51/// wraps in struct literals.
52#[allow(
53    non_snake_case,
54    reason = "mirrors the variant name for struct literals"
55)]
56pub fn Set<T>(value: T) -> ActiveValue<T> {
57    ActiveValue::Set(value)
58}
59
60/// Builds an [`ActiveValue::Unchanged`].
61///
62/// Named in `UpperCamelCase` for the same reason as [`Set`].
63#[allow(
64    non_snake_case,
65    reason = "mirrors the variant name for struct literals"
66)]
67pub fn Unchanged<T>(value: T) -> ActiveValue<T> {
68    ActiveValue::Unchanged(value)
69}
70
71/// The [`ActiveValue::NotSet`] constant, usable in struct literals.
72#[allow(
73    non_upper_case_globals,
74    reason = "mirrors the variant name for struct literals"
75)]
76pub const NotSet: ActiveValue<()> = ActiveValue::NotSet;
77
78impl<T> ActiveValue<T> {
79    /// Whether the attribute is `Set`.
80    pub fn is_set(&self) -> bool {
81        matches!(self, ActiveValue::Set(_))
82    }
83
84    /// Whether the attribute is `Unchanged`.
85    pub fn is_unchanged(&self) -> bool {
86        matches!(self, ActiveValue::Unchanged(_))
87    }
88
89    /// Whether the attribute is `NotSet`.
90    pub fn is_not_set(&self) -> bool {
91        matches!(self, ActiveValue::NotSet)
92    }
93
94    /// The value, whether `Set` or `Unchanged`.
95    pub fn as_ref(&self) -> Option<&T> {
96        match self {
97            ActiveValue::Set(v) | ActiveValue::Unchanged(v) => Some(v),
98            ActiveValue::NotSet => None,
99        }
100    }
101
102    /// Takes the value out, leaving `NotSet` behind.
103    pub fn take(&mut self) -> Option<T> {
104        match std::mem::replace(self, ActiveValue::NotSet) {
105            ActiveValue::Set(v) | ActiveValue::Unchanged(v) => Some(v),
106            ActiveValue::NotSet => None,
107        }
108    }
109
110    /// Consumes the attribute and returns its value, whether `Set` or `Unchanged`.
111    pub fn into_value(self) -> Option<T> {
112        match self {
113            ActiveValue::Set(v) | ActiveValue::Unchanged(v) => Some(v),
114            ActiveValue::NotSet => None,
115        }
116    }
117
118    /// Marks the attribute `Unchanged`, keeping its value; used after a successful write.
119    pub fn reset(&mut self) {
120        // The state is moved out exactly once: a second `mem::replace` would
121        // read the `NotSet` placeholder and lose an `Unchanged` value.
122        *self = match std::mem::replace(self, ActiveValue::NotSet) {
123            ActiveValue::Set(v) | ActiveValue::Unchanged(v) => ActiveValue::Unchanged(v),
124            ActiveValue::NotSet => ActiveValue::NotSet,
125        };
126    }
127
128    /// Maps the inner value, preserving the attribute state.
129    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> ActiveValue<U> {
130        match self {
131            ActiveValue::Set(v) => ActiveValue::Set(f(v)),
132            ActiveValue::Unchanged(v) => ActiveValue::Unchanged(f(v)),
133            ActiveValue::NotSet => ActiveValue::NotSet,
134        }
135    }
136}
137
138impl<T: PartialEq> ActiveValue<T> {
139    /// Sets `value` unless it equals the current `Unchanged` value, in which
140    /// case the attribute stays `Unchanged` and is not written back.
141    pub fn set_if_not_equals(&mut self, value: T) {
142        match self {
143            ActiveValue::Unchanged(current) if *current == value => {}
144            _ => *self = ActiveValue::Set(value),
145        }
146    }
147}
148
149impl<T> From<T> for ActiveValue<T> {
150    fn from(value: T) -> Self {
151        ActiveValue::Set(value)
152    }
153}
154
155/// Conversion into an entity's active model, from the model or as identity.
156pub trait IntoActiveModel<A: ActiveModelTrait> {
157    /// Converts into the active model.
158    fn into_active_model(self) -> A;
159}
160
161impl<A: ActiveModelTrait> IntoActiveModel<A> for A {
162    fn into_active_model(self) -> A {
163        self
164    }
165}
166
167/// Conversion of one field of a plain struct into an [`ActiveValue`], as
168/// applied by `DeriveIntoActiveModel`.
169///
170/// A plain value becomes `Set`. An `Option` wrapping the attribute type
171/// becomes `Set` when `Some` and `NotSet` when `None`, so that an optional
172/// field of a request leaves the column alone; an `Option` that *is* the
173/// attribute type — a nullable column — is always `Set`, `None` included,
174/// and `Option<Option<T>>` is the form that leaves a nullable column alone.
175/// Rust picks the right rule from the attribute type the active model
176/// declares.
177pub trait IntoActiveValue<T> {
178    /// Converts into the attribute state.
179    fn into_active_value(self) -> ActiveValue<T>;
180}
181
182impl<T: crate::types::TursoType> IntoActiveValue<T> for T {
183    fn into_active_value(self) -> ActiveValue<T> {
184        ActiveValue::Set(self)
185    }
186}
187
188impl<T: crate::types::TursoType> IntoActiveValue<T> for Option<T> {
189    fn into_active_value(self) -> ActiveValue<T> {
190        match self {
191            Some(v) => ActiveValue::Set(v),
192            None => ActiveValue::NotSet,
193        }
194    }
195}
196
197/// Conversion of an active model back into the plain model.
198///
199/// Implemented by `DeriveEntityModel` on the generated `ActiveModel`.
200pub trait TryIntoModel<M> {
201    /// Builds the model from the attribute values.
202    ///
203    /// # Errors
204    ///
205    /// Returns [`DbErr::AttrNotSet`] naming the first attribute that is
206    /// `NotSet`.
207    fn try_into_model(self) -> Result<M>;
208}
209
210/// The mutable form of an entity, derived by `DeriveEntityModel`.
211#[async_trait]
212pub trait ActiveModelTrait: Clone + Send + Sync + std::fmt::Debug + Default {
213    /// The entity this active model belongs to.
214    type Entity: EntityTrait<ActiveModel = Self>;
215
216    /// Reads an attribute as a SQL value together with its state.
217    fn get(&self, column: <Self::Entity as EntityTrait>::Column) -> ActiveValue<Value>;
218
219    /// Sets an attribute from a SQL value, marking it `Set`.
220    ///
221    /// # Errors
222    ///
223    /// Returns [`DbErr::Type`] when the value cannot be decoded as the field type.
224    fn set(&mut self, column: <Self::Entity as EntityTrait>::Column, value: Value) -> Result<()>;
225
226    /// Marks an attribute `NotSet`.
227    fn not_set(&mut self, column: <Self::Entity as EntityTrait>::Column);
228
229    /// Whether an attribute is `NotSet`.
230    fn is_not_set(&self, column: <Self::Entity as EntityTrait>::Column) -> bool;
231
232    /// Marks an attribute `Unchanged`.
233    fn reset(&mut self, column: <Self::Entity as EntityTrait>::Column);
234
235    /// Marks every attribute `Unchanged`.
236    #[must_use]
237    fn reset_all(mut self) -> Self {
238        for c in <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter() {
239            self.reset(c);
240        }
241        self
242    }
243
244    /// Whether any attribute is `Set`.
245    fn is_changed(&self) -> bool {
246        <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter()
247            .any(|c| self.get(c).is_set())
248    }
249
250    /// Sets every attribute named by a key of the JSON object `json`,
251    /// matching keys against column names.
252    ///
253    /// Keys that are not columns are ignored. Numbers, strings, booleans
254    /// and `null` map onto the storage classes; an array or an object is
255    /// stored as its JSON text, which is how a JSON column expects it.
256    ///
257    /// # Errors
258    ///
259    /// Returns [`DbErr::Json`] when `json` is not an object; [`DbErr::Type`]
260    /// when a value cannot be decoded as the attribute type.
261    #[cfg(feature = "with-json")]
262    #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
263    fn set_from_json(&mut self, json: serde_json::Value) -> Result<()> {
264        let serde_json::Value::Object(map) = json else {
265            return Err(DbErr::Json("expected a JSON object".into()));
266        };
267        for c in <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter() {
268            if let Some(v) =
269                map.get(<<Self::Entity as EntityTrait>::Column as super::IdenStatic>::as_str(&c))
270            {
271                self.set(c, json_to_value(v))?;
272            }
273        }
274        Ok(())
275    }
276
277    /// Builds an active model with every attribute named in `json` set and
278    /// the others `NotSet`; see [`set_from_json`](Self::set_from_json).
279    ///
280    /// # Errors
281    ///
282    /// Returns the errors of [`set_from_json`](Self::set_from_json).
283    #[cfg(feature = "with-json")]
284    #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
285    fn from_json(json: serde_json::Value) -> Result<Self> {
286        let mut am = Self::default();
287        am.set_from_json(json)?;
288        Ok(am)
289    }
290
291    /// The primary key values, or `None` when any key attribute is `NotSet`.
292    fn get_primary_key_value(&self) -> Option<Vec<Value>> {
293        use super::primary_key::PrimaryKeyToColumn;
294        let mut values = Vec::new();
295        for pk in <<Self::Entity as EntityTrait>::PrimaryKey as super::Iterable>::iter() {
296            let value = self.get(pk.into_column()).into_value()?;
297            values.push(value);
298        }
299        Some(values)
300    }
301
302    /// Inserts the active model and returns the stored model via `INSERT ... RETURNING *`.
303    ///
304    /// The [`ActiveModelBehavior`] hooks run around the statement.
305    ///
306    /// # Errors
307    ///
308    /// Returns [`DbErr::RecordNotInserted`] when the statement inserted
309    /// nothing; [`DbErr::Driver`] when the statement or the decoding of the
310    /// returned row fails; any error raised by the hooks.
311    async fn insert<C: ConnectionTrait>(
312        self,
313        db: &C,
314    ) -> Result<<Self::Entity as EntityTrait>::Model>
315    where
316        Self: ActiveModelBehavior,
317    {
318        let am = <Self as ActiveModelBehavior>::before_save(self, db, true).await?;
319        let model = Insert::<Self>::one(am).exec_with_returning(db).await?;
320        <Self as ActiveModelBehavior>::after_save(model, db, true).await
321    }
322
323    /// Updates the row matched by the primary key and returns the stored model.
324    ///
325    /// Only `Set` attributes are written; with none, the row is simply
326    /// fetched. The [`ActiveModelBehavior`] hooks run around the statement.
327    ///
328    /// # Errors
329    ///
330    /// Returns [`DbErr::PrimaryKeyNotSet`] when a key attribute is `NotSet`;
331    /// [`DbErr::RecordNotUpdated`] when no row matches the key;
332    /// [`DbErr::Driver`] when the statement or the decoding of the returned
333    /// row fails; any error raised by the hooks.
334    async fn update<C: ConnectionTrait>(
335        self,
336        db: &C,
337    ) -> Result<<Self::Entity as EntityTrait>::Model>
338    where
339        Self: ActiveModelBehavior,
340    {
341        let am = <Self as ActiveModelBehavior>::before_save(self, db, false).await?;
342        let model = UpdateOne::new(am).exec(db).await?;
343        <Self as ActiveModelBehavior>::after_save(model, db, false).await
344    }
345
346    /// Inserts when the primary key is `NotSet` or `Set`, updates when it is `Unchanged`.
347    ///
348    /// Returns the active model with every attribute `Unchanged`, so that it
349    /// can be modified and saved again.
350    ///
351    /// # Errors
352    ///
353    /// Returns the errors of [`insert`](Self::insert) or
354    /// [`update`](Self::update), whichever ran.
355    async fn save<C: ConnectionTrait>(self, db: &C) -> Result<Self>
356    where
357        Self: ActiveModelBehavior,
358        <Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
359    {
360        let model = if self.is_update() {
361            self.update(db).await?
362        } else {
363            self.insert(db).await?
364        };
365        Ok(model.into_active_model())
366    }
367
368    /// Whether [`save`](Self::save) would update: every primary-key attribute is `Unchanged`.
369    fn is_update(&self) -> bool {
370        use super::primary_key::PrimaryKeyToColumn;
371        let mut keys = <<Self::Entity as EntityTrait>::PrimaryKey as super::Iterable>::iter();
372        // An entity without key columns can never be updated by key, and
373        // `all` on an empty iterator would wrongly say yes.
374        let Some(first) = keys.next() else {
375            return false;
376        };
377        std::iter::once(first)
378            .chain(keys)
379            .all(|pk| self.get(pk.into_column()).is_unchanged())
380    }
381
382    /// Deletes the row matched by the primary key.
383    ///
384    /// The [`ActiveModelBehavior`] hooks run around the statement.
385    ///
386    /// # Errors
387    ///
388    /// Returns [`DbErr::PrimaryKeyNotSet`] when a key attribute is `NotSet`;
389    /// [`DbErr::Driver`] when the statement fails; any error raised by the
390    /// hooks.
391    async fn delete<C: ConnectionTrait>(self, db: &C) -> Result<DeleteResult>
392    where
393        Self: ActiveModelBehavior,
394    {
395        let am = <Self as ActiveModelBehavior>::before_delete(self, db).await?;
396        let result = crate::query::DeleteOne::new(am.clone()).exec(db).await?;
397        <Self as ActiveModelBehavior>::after_delete(am, db).await?;
398        Ok(result)
399    }
400}
401
402/// Hooks around writes, with no-op defaults.
403///
404/// They run around the methods of [`ActiveModelTrait`] only (`insert`,
405/// `update`, `save` and `delete`); statements built from the entity, such
406/// as `Entity::insert` or `Entity::delete_many`, bypass them. An error from an `after_*` hook is returned once the
407/// statement has run, so outside a transaction the write stays applied.
408///
409/// Implement with an empty body to accept the defaults:
410///
411/// ```ignore
412/// impl ActiveModelBehavior for ActiveModel {}
413/// ```
414#[async_trait]
415pub trait ActiveModelBehavior: ActiveModelTrait {
416    /// Called before `insert` (`insert == true`) or `update`; may rewrite the active model.
417    ///
418    /// # Errors
419    ///
420    /// Returns whatever the implementation chooses to fail with; the
421    /// default never fails.
422    async fn before_save<C: ConnectionTrait>(self, _db: &C, _insert: bool) -> Result<Self> {
423        Ok(self)
424    }
425
426    /// Called after a successful `insert` or `update` with the stored model.
427    ///
428    /// # Errors
429    ///
430    /// Returns whatever the implementation chooses to fail with; the
431    /// default never fails.
432    async fn after_save<C: ConnectionTrait>(
433        model: <Self::Entity as EntityTrait>::Model,
434        _db: &C,
435        _insert: bool,
436    ) -> Result<<Self::Entity as EntityTrait>::Model> {
437        Ok(model)
438    }
439
440    /// Called before `delete`; may rewrite the active model.
441    ///
442    /// # Errors
443    ///
444    /// Returns whatever the implementation chooses to fail with; the
445    /// default never fails.
446    async fn before_delete<C: ConnectionTrait>(self, _db: &C) -> Result<Self> {
447        Ok(self)
448    }
449
450    /// Called after a successful `delete`.
451    ///
452    /// # Errors
453    ///
454    /// Returns whatever the implementation chooses to fail with; the
455    /// default never fails.
456    async fn after_delete<C: ConnectionTrait>(self, _db: &C) -> Result<Self> {
457        Ok(self)
458    }
459}
460
461/// Flattens a JSON value onto a storage class for [`ActiveModelTrait::set_from_json`].
462///
463/// Integers that fit `i64` stay integers, other numbers become reals, and
464/// compound values are stored as their JSON text.
465#[cfg(feature = "with-json")]
466fn json_to_value(json: &serde_json::Value) -> Value {
467    match json {
468        serde_json::Value::Null => Value::Null,
469        serde_json::Value::Bool(b) => Value::Integer(i64::from(*b)),
470        serde_json::Value::Number(n) => n
471            .as_i64()
472            .map(Value::Integer)
473            .or_else(|| n.as_f64().map(Value::Real))
474            .unwrap_or(Value::Null),
475        serde_json::Value::String(s) => Value::Text(s.clone()),
476        compound => Value::Text(compound.to_string()),
477    }
478}
479
480/// Decodes a SQL value into a field type for generated `set` impls.
481///
482/// The driver's `FromValue` decoders take the builder's [`Value`] directly,
483/// so this only maps the error type.
484///
485/// # Errors
486///
487/// Returns [`DbErr::Type`] when the value cannot be decoded as `T`.
488pub fn decode_field<T: turso_orm_driver::FromValue>(column: &str, value: Value) -> Result<T> {
489    T::from_value(value, column).map_err(|e| DbErr::Type(e.to_string()))
490}
491
492#[cfg(test)]
493mod tests {
494    use super::ActiveValue;
495
496    /// `reset` turns a `Set` value into `Unchanged`, keeping the value.
497    #[test]
498    fn reset_set_becomes_unchanged() {
499        let mut value = ActiveValue::Set(42);
500        value.reset();
501        assert_eq!(value, ActiveValue::Unchanged(42));
502    }
503
504    /// `reset` leaves an `Unchanged` value as it is.
505    #[test]
506    fn reset_unchanged_stays_unchanged() {
507        let mut value = ActiveValue::Unchanged(42);
508        value.reset();
509        assert_eq!(value, ActiveValue::Unchanged(42));
510    }
511
512    /// `reset` leaves a `NotSet` attribute `NotSet`.
513    #[test]
514    fn reset_not_set_stays_not_set() {
515        let mut value = ActiveValue::<i32>::NotSet;
516        value.reset();
517        assert_eq!(value, ActiveValue::NotSet);
518    }
519
520    /// `reset` is idempotent on every state.
521    #[test]
522    fn reset_is_idempotent() {
523        for start in [
524            ActiveValue::Set(1),
525            ActiveValue::Unchanged(1),
526            ActiveValue::NotSet,
527        ] {
528            let mut once = start.clone();
529            once.reset();
530            let mut twice = once.clone();
531            twice.reset();
532            assert_eq!(once, twice);
533        }
534    }
535}