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}