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        // Both arms go through `mem::replace` because the value must be
121        // moved out of `self` before `self` is overwritten.
122        if let ActiveValue::Set(v) = std::mem::replace(self, ActiveValue::NotSet) {
123            *self = ActiveValue::Unchanged(v);
124        } else if let ActiveValue::Unchanged(v) = std::mem::replace(self, ActiveValue::NotSet) {
125            *self = ActiveValue::Unchanged(v);
126        }
127    }
128
129    /// Maps the inner value, preserving the attribute state.
130    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> ActiveValue<U> {
131        match self {
132            ActiveValue::Set(v) => ActiveValue::Set(f(v)),
133            ActiveValue::Unchanged(v) => ActiveValue::Unchanged(f(v)),
134            ActiveValue::NotSet => ActiveValue::NotSet,
135        }
136    }
137}
138
139impl<T: PartialEq> ActiveValue<T> {
140    /// Sets `value` unless it equals the current `Unchanged` value, in which
141    /// case the attribute stays `Unchanged` and is not written back.
142    pub fn set_if_not_equals(&mut self, value: T) {
143        match self {
144            ActiveValue::Unchanged(current) if *current == value => {}
145            _ => *self = ActiveValue::Set(value),
146        }
147    }
148}
149
150impl<T> From<T> for ActiveValue<T> {
151    fn from(value: T) -> Self {
152        ActiveValue::Set(value)
153    }
154}
155
156/// Conversion into an entity's active model, from the model or as identity.
157pub trait IntoActiveModel<A: ActiveModelTrait> {
158    /// Converts into the active model.
159    fn into_active_model(self) -> A;
160}
161
162impl<A: ActiveModelTrait> IntoActiveModel<A> for A {
163    fn into_active_model(self) -> A {
164        self
165    }
166}
167
168/// Conversion of one field of a plain struct into an [`ActiveValue`], as
169/// applied by `DeriveIntoActiveModel`.
170///
171/// A plain value becomes `Set`. An `Option` wrapping the attribute type
172/// becomes `Set` when `Some` and `NotSet` when `None`, so that an optional
173/// field of a request leaves the column alone; an `Option` that *is* the
174/// attribute type — a nullable column — is always `Set`, `None` included,
175/// and `Option<Option<T>>` is the form that leaves a nullable column alone.
176/// Rust picks the right rule from the attribute type the active model
177/// declares.
178pub trait IntoActiveValue<T> {
179    /// Converts into the attribute state.
180    fn into_active_value(self) -> ActiveValue<T>;
181}
182
183impl<T: crate::types::TursoType> IntoActiveValue<T> for T {
184    fn into_active_value(self) -> ActiveValue<T> {
185        ActiveValue::Set(self)
186    }
187}
188
189impl<T: crate::types::TursoType> IntoActiveValue<T> for Option<T> {
190    fn into_active_value(self) -> ActiveValue<T> {
191        match self {
192            Some(v) => ActiveValue::Set(v),
193            None => ActiveValue::NotSet,
194        }
195    }
196}
197
198/// Conversion of an active model back into the plain model.
199///
200/// Implemented by `DeriveEntityModel` on the generated `ActiveModel`.
201pub trait TryIntoModel<M> {
202    /// Builds the model from the attribute values.
203    ///
204    /// # Errors
205    ///
206    /// Returns [`DbErr::AttrNotSet`] naming the first attribute that is
207    /// `NotSet`.
208    fn try_into_model(self) -> Result<M>;
209}
210
211/// The mutable form of an entity, derived by `DeriveEntityModel`.
212#[async_trait]
213pub trait ActiveModelTrait: Clone + Send + Sync + std::fmt::Debug + Default {
214    /// The entity this active model belongs to.
215    type Entity: EntityTrait<ActiveModel = Self>;
216
217    /// Reads an attribute as a SQL value together with its state.
218    fn get(&self, column: <Self::Entity as EntityTrait>::Column) -> ActiveValue<Value>;
219
220    /// Sets an attribute from a SQL value, marking it `Set`.
221    ///
222    /// # Errors
223    ///
224    /// Returns [`DbErr::Type`] when the value cannot be decoded as the field type.
225    fn set(&mut self, column: <Self::Entity as EntityTrait>::Column, value: Value) -> Result<()>;
226
227    /// Marks an attribute `NotSet`.
228    fn not_set(&mut self, column: <Self::Entity as EntityTrait>::Column);
229
230    /// Whether an attribute is `NotSet`.
231    fn is_not_set(&self, column: <Self::Entity as EntityTrait>::Column) -> bool;
232
233    /// Marks an attribute `Unchanged`.
234    fn reset(&mut self, column: <Self::Entity as EntityTrait>::Column);
235
236    /// Marks every attribute `Unchanged`.
237    #[must_use]
238    fn reset_all(mut self) -> Self {
239        for c in <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter() {
240            self.reset(c);
241        }
242        self
243    }
244
245    /// Whether any attribute is `Set`.
246    fn is_changed(&self) -> bool {
247        <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter()
248            .any(|c| self.get(c).is_set())
249    }
250
251    /// Sets every attribute named by a key of the JSON object `json`,
252    /// matching keys against column names.
253    ///
254    /// Keys that are not columns are ignored. Numbers, strings, booleans
255    /// and `null` map onto the storage classes; an array or an object is
256    /// stored as its JSON text, which is how a JSON column expects it.
257    ///
258    /// # Errors
259    ///
260    /// Returns [`DbErr::Json`] when `json` is not an object; [`DbErr::Type`]
261    /// when a value cannot be decoded as the attribute type.
262    #[cfg(feature = "with-json")]
263    #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
264    fn set_from_json(&mut self, json: serde_json::Value) -> Result<()> {
265        let serde_json::Value::Object(map) = json else {
266            return Err(DbErr::Json("expected a JSON object".into()));
267        };
268        for c in <<Self::Entity as EntityTrait>::Column as super::Iterable>::iter() {
269            if let Some(v) =
270                map.get(<<Self::Entity as EntityTrait>::Column as super::IdenStatic>::as_str(&c))
271            {
272                self.set(c, json_to_value(v))?;
273            }
274        }
275        Ok(())
276    }
277
278    /// Builds an active model with every attribute named in `json` set and
279    /// the others `NotSet`; see [`set_from_json`](Self::set_from_json).
280    ///
281    /// # Errors
282    ///
283    /// Returns the errors of [`set_from_json`](Self::set_from_json).
284    #[cfg(feature = "with-json")]
285    #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
286    fn from_json(json: serde_json::Value) -> Result<Self> {
287        let mut am = Self::default();
288        am.set_from_json(json)?;
289        Ok(am)
290    }
291
292    /// The primary key values, or `None` when any key attribute is `NotSet`.
293    fn get_primary_key_value(&self) -> Option<Vec<Value>> {
294        use super::primary_key::PrimaryKeyToColumn;
295        let mut values = Vec::new();
296        for pk in <<Self::Entity as EntityTrait>::PrimaryKey as super::Iterable>::iter() {
297            let value = self.get(pk.into_column()).into_value()?;
298            values.push(value);
299        }
300        Some(values)
301    }
302
303    /// Inserts the active model and returns the stored model via `INSERT ... RETURNING *`.
304    ///
305    /// The [`ActiveModelBehavior`] hooks run around the statement.
306    ///
307    /// # Errors
308    ///
309    /// Returns [`DbErr::RecordNotInserted`] when the statement inserted
310    /// nothing; [`DbErr::Driver`] when the statement or the decoding of the
311    /// returned row fails; any error raised by the hooks.
312    async fn insert<C: ConnectionTrait>(
313        self,
314        db: &C,
315    ) -> Result<<Self::Entity as EntityTrait>::Model>
316    where
317        Self: ActiveModelBehavior,
318    {
319        let am = <Self as ActiveModelBehavior>::before_save(self, db, true).await?;
320        let model = Insert::<Self>::one(am).exec_with_returning(db).await?;
321        <Self as ActiveModelBehavior>::after_save(model, db, true).await
322    }
323
324    /// Updates the row matched by the primary key and returns the stored model.
325    ///
326    /// Only `Set` attributes are written; with none, the row is simply
327    /// fetched. The [`ActiveModelBehavior`] hooks run around the statement.
328    ///
329    /// # Errors
330    ///
331    /// Returns [`DbErr::PrimaryKeyNotSet`] when a key attribute is `NotSet`;
332    /// [`DbErr::RecordNotUpdated`] when no row matches the key;
333    /// [`DbErr::Driver`] when the statement or the decoding of the returned
334    /// row fails; any error raised by the hooks.
335    async fn update<C: ConnectionTrait>(
336        self,
337        db: &C,
338    ) -> Result<<Self::Entity as EntityTrait>::Model>
339    where
340        Self: ActiveModelBehavior,
341    {
342        let am = <Self as ActiveModelBehavior>::before_save(self, db, false).await?;
343        let model = UpdateOne::new(am).exec(db).await?;
344        <Self as ActiveModelBehavior>::after_save(model, db, false).await
345    }
346
347    /// Inserts when the primary key is `NotSet` or `Set`, updates when it is `Unchanged`.
348    ///
349    /// Returns the active model with every attribute `Unchanged`, so that it
350    /// can be modified and saved again.
351    ///
352    /// # Errors
353    ///
354    /// Returns the errors of [`insert`](Self::insert) or
355    /// [`update`](Self::update), whichever ran.
356    async fn save<C: ConnectionTrait>(self, db: &C) -> Result<Self>
357    where
358        Self: ActiveModelBehavior,
359        <Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
360    {
361        let model = if self.is_update() {
362            self.update(db).await?
363        } else {
364            self.insert(db).await?
365        };
366        Ok(model.into_active_model())
367    }
368
369    /// Whether [`save`](Self::save) would update: every primary-key attribute is `Unchanged`.
370    fn is_update(&self) -> bool {
371        use super::primary_key::PrimaryKeyToColumn;
372        let mut keys = <<Self::Entity as EntityTrait>::PrimaryKey as super::Iterable>::iter();
373        // An entity without key columns can never be updated by key, and
374        // `all` on an empty iterator would wrongly say yes.
375        let Some(first) = keys.next() else {
376            return false;
377        };
378        std::iter::once(first)
379            .chain(keys)
380            .all(|pk| self.get(pk.into_column()).is_unchanged())
381    }
382
383    /// Deletes the row matched by the primary key.
384    ///
385    /// The [`ActiveModelBehavior`] hooks run around the statement.
386    ///
387    /// # Errors
388    ///
389    /// Returns [`DbErr::PrimaryKeyNotSet`] when a key attribute is `NotSet`;
390    /// [`DbErr::Driver`] when the statement fails; any error raised by the
391    /// hooks.
392    async fn delete<C: ConnectionTrait>(self, db: &C) -> Result<DeleteResult>
393    where
394        Self: ActiveModelBehavior,
395    {
396        let am = <Self as ActiveModelBehavior>::before_delete(self, db).await?;
397        let result = crate::query::DeleteOne::new(am.clone()).exec(db).await?;
398        <Self as ActiveModelBehavior>::after_delete(am, db).await?;
399        Ok(result)
400    }
401}
402
403/// Hooks around writes, with no-op defaults.
404///
405/// Implement with an empty body to accept the defaults:
406///
407/// ```ignore
408/// impl ActiveModelBehavior for ActiveModel {}
409/// ```
410#[async_trait]
411pub trait ActiveModelBehavior: ActiveModelTrait {
412    /// Called before `insert` (`insert == true`) or `update`; may rewrite the active model.
413    ///
414    /// # Errors
415    ///
416    /// Returns whatever the implementation chooses to fail with; the
417    /// default never fails.
418    async fn before_save<C: ConnectionTrait>(self, _db: &C, _insert: bool) -> Result<Self> {
419        Ok(self)
420    }
421
422    /// Called after a successful `insert` or `update` with the stored model.
423    ///
424    /// # Errors
425    ///
426    /// Returns whatever the implementation chooses to fail with; the
427    /// default never fails.
428    async fn after_save<C: ConnectionTrait>(
429        model: <Self::Entity as EntityTrait>::Model,
430        _db: &C,
431        _insert: bool,
432    ) -> Result<<Self::Entity as EntityTrait>::Model> {
433        Ok(model)
434    }
435
436    /// Called before `delete`; may rewrite the active model.
437    ///
438    /// # Errors
439    ///
440    /// Returns whatever the implementation chooses to fail with; the
441    /// default never fails.
442    async fn before_delete<C: ConnectionTrait>(self, _db: &C) -> Result<Self> {
443        Ok(self)
444    }
445
446    /// Called after a successful `delete`.
447    ///
448    /// # Errors
449    ///
450    /// Returns whatever the implementation chooses to fail with; the
451    /// default never fails.
452    async fn after_delete<C: ConnectionTrait>(self, _db: &C) -> Result<Self> {
453        Ok(self)
454    }
455}
456
457/// Flattens a JSON value onto a storage class for [`ActiveModelTrait::set_from_json`].
458///
459/// Integers that fit `i64` stay integers, other numbers become reals, and
460/// compound values are stored as their JSON text.
461#[cfg(feature = "with-json")]
462fn json_to_value(json: &serde_json::Value) -> Value {
463    match json {
464        serde_json::Value::Null => Value::Null,
465        serde_json::Value::Bool(b) => Value::Integer(i64::from(*b)),
466        serde_json::Value::Number(n) => n
467            .as_i64()
468            .map(Value::Integer)
469            .or_else(|| n.as_f64().map(Value::Real))
470            .unwrap_or(Value::Null),
471        serde_json::Value::String(s) => Value::Text(s.clone()),
472        compound => Value::Text(compound.to_string()),
473    }
474}
475
476/// Decodes a SQL value into a field type for generated `set` impls.
477///
478/// The driver's `FromValue` decoders take the builder's [`Value`] directly,
479/// so this only maps the error type.
480///
481/// # Errors
482///
483/// Returns [`DbErr::Type`] when the value cannot be decoded as `T`.
484pub fn decode_field<T: turso_orm_driver::FromValue>(column: &str, value: Value) -> Result<T> {
485    T::from_value(value, column).map_err(|e| DbErr::Type(e.to_string()))
486}