siderite_orm/ops.rs
1//! Instance operations: [`ModelOps`] gives every [`Model`] `save`, `delete`
2//! and `refresh` (Django `Model.save()` / `delete()` / `refresh_from_db()`).
3//!
4//! # Semantics
5//!
6//! * **One statement, signals around it.** Each operation is a single SQL
7//! statement (`save` may be two, see below). `save` sends `pre_save` before
8//! and `post_save` (with the `created` flag) after it; `delete` sends
9//! `pre_delete` before and `post_delete` after a row was removed. Receivers
10//! are those connected to [`Db::signals`]; see [`signals`](crate::signals)
11//! for ordering and failure rules. A failing `pre_*` receiver aborts the
12//! operation; a failing `post_*` receiver errors after the statement ran,
13//! so wrap the call in [`Db::transaction`] to roll it back. Use
14//! [`Db::on_commit`] for work that must run only after the surrounding
15//! transaction commits. Bulk `QuerySet` writes send no signals.
16//! * **Atomicity.** A single statement is atomic. `save` on a manual primary
17//! key runs `UPDATE` and, if no row matched, `INSERT`; outside a transaction
18//! another writer can slip in between the two, so wrap concurrent upserts in
19//! [`Db::transaction`] (or use `QuerySet::get_or_create`).
20//! * **Timestamps.** `auto_now_add` columns are stamped on insert and
21//! `auto_now` columns on every save, with the current UTC time.
22//! * **Stored state wins.** `save` reads the stored row back (`RETURNING *`)
23//! and replaces `*self`, so database defaults, generated keys and stamped
24//! timestamps are visible immediately. Cached related objects of
25//! [`ForeignKey`](crate::ForeignKey) fields are dropped by that replacement.
26
27use crate::db::Db;
28use crate::error::{OrmError, QueryError};
29use crate::model::Model;
30use crate::persist::{insert_one, pk_filter, update_one};
31use crate::signals::SignalKind;
32use crate::write::{DeletePlan, WritePlan};
33use std::future::Future;
34
35/// Persistence operations available on every [`Model`].
36pub trait ModelOps: Model {
37 /// Insert or update this object.
38 ///
39 /// An [`is_unsaved`](Model::is_unsaved) object is inserted (generated
40 /// columns omitted) and receives its key. Otherwise the row is updated by
41 /// primary key, falling back to an insert when no row exists.
42 ///
43 /// # Errors
44 /// Capability or backend errors; [`BackendError::Constraint`](crate::BackendError::Constraint)
45 /// for unique / foreign-key violations; [`OrmError::Signal`] when a
46 /// `pre_save` receiver aborts the save (nothing is written) or a
47 /// `post_save` receiver fails (the row is already written).
48 fn save(&mut self, db: &Db) -> impl Future<Output = Result<(), OrmError>> + Send;
49
50 /// Delete this object's row, returning the number of rows removed
51 /// (`0` if it no longer exists). Dependent rows follow the database's
52 /// foreign-key actions.
53 ///
54 /// # Errors
55 /// Capability or backend errors (e.g. a `RESTRICT` foreign key);
56 /// [`OrmError::Signal`] from `pre_delete` (aborts) or `post_delete`
57 /// (after the row is gone). `post_delete` is skipped when no row matched.
58 fn delete(&self, db: &Db) -> impl Future<Output = Result<u64, OrmError>> + Send;
59
60 /// Reload every column from the database, replacing `*self`.
61 ///
62 /// # Errors
63 /// [`QueryError::DoesNotExist`] if the row is gone.
64 fn refresh(&mut self, db: &Db) -> impl Future<Output = Result<(), OrmError>> + Send;
65}
66
67impl<M: Model> ModelOps for M {
68 async fn save(&mut self, db: &Db) -> Result<(), OrmError> {
69 let signals = db.signals();
70 signals
71 .send(db, &*self, SignalKind::PreSave, None, &[])
72 .await?;
73 let created = if self.is_unsaved() || !update_one(db, self).await? {
74 insert_one(db, self).await?;
75 true
76 } else {
77 false
78 };
79 signals
80 .send(db, &*self, SignalKind::PostSave { created }, None, &[])
81 .await
82 }
83
84 async fn delete(&self, db: &Db) -> Result<u64, OrmError> {
85 let plan = WritePlan::Delete(DeletePlan {
86 table: M::META.table.into(),
87 filter: Some(pk_filter::<M>(&self.pk())?),
88 returning: Vec::new(),
89 });
90 let signals = db.signals();
91 signals
92 .send(db, self, SignalKind::PreDelete, None, &[])
93 .await?;
94 let removed = db.execute(&plan).await?.rows_affected;
95 if removed > 0 {
96 signals
97 .send(db, self, SignalKind::PostDelete, None, &[])
98 .await?;
99 }
100 Ok(removed)
101 }
102
103 async fn refresh(&mut self, db: &Db) -> Result<(), OrmError> {
104 let fresh = M::objects(db)
105 .filter(pk_filter::<M>(&self.pk())?)
106 .first()
107 .await?
108 .ok_or(QueryError::DoesNotExist)?;
109 *self = fresh;
110 Ok(())
111 }
112}