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}