rust_query/
mutable.rs

1use std::{
2    cell::OnceCell,
3    marker::PhantomData,
4    ops::{Deref, DerefMut},
5    panic::AssertUnwindSafe,
6};
7
8use crate::{IntoExpr, Table, TableRow, Transaction};
9
10/// [Mutable] access to columns of a single table row.
11///
12/// The whole row is retrieved and can be inspected from Rust code.
13/// However, only rows that are not used in a `#[unique]`
14/// constraint can be updated directly by dereferencing [Mutable].
15///
16/// To update columns with a unique constraint, you have to use [Mutable::unique].
17pub struct Mutable<'transaction, T: Table> {
18    cell: OnceCell<MutableInner<T>>,
19    row_id: TableRow<T>,
20    _txn: PhantomData<&'transaction mut Transaction<T::Schema>>,
21}
22
23struct MutableInner<T: Table> {
24    val: T::Mutable,
25    any_update: bool,
26}
27
28impl<T: Table> MutableInner<T> {
29    fn new(row_id: TableRow<T>) -> Self {
30        Self {
31            val: Transaction::new_ref().query_one(T::select_mutable(row_id.into_expr())),
32            any_update: false,
33        }
34    }
35}
36
37impl<'transaction, T: Table> Mutable<'transaction, T> {
38    pub(crate) fn new(inner: T::Mutable, row_id: TableRow<T>) -> Self {
39        Self {
40            cell: OnceCell::from(MutableInner {
41                val: inner,
42                any_update: false,
43            }),
44            row_id,
45            _txn: PhantomData,
46        }
47    }
48
49    /// Turn the [Mutable] into a [TableRow].
50    ///
51    /// This will end the lifetime of the [Mutable], which is useful since
52    /// [Mutable] does not have a non lexical lifetime, because of the [Drop] impl.
53    ///
54    /// If you do not need the [TableRow], then it is also possible to just call [drop].
55    pub fn into_table_row(self) -> TableRow<T> {
56        self.row_id
57    }
58
59    /// Update unique constraint columns.
60    ///
61    /// When the update succeeds, this function returns [Ok], when it fails it returns [Err] with one of
62    /// three conflict types:
63    /// - 0 unique constraints => [std::convert::Infallible]
64    /// - 1 unique constraint => [TableRow] reference to the conflicting table row.
65    /// - 2+ unique constraints => `()` no further information is provided.
66    ///
67    /// If any of the changes made inside the closure conflict with an existing row, then all changes
68    /// made inside the closure are reverted.
69    ///
70    /// If the closure panics, then all changes made inside the closure are also reverted.
71    /// Applying those changes is not possible, as conflicts can not be reported if there is a panic.
72    pub fn unique<O>(
73        &mut self,
74        f: impl FnOnce(&mut <T::Mutable as Deref>::Target) -> O,
75    ) -> Result<O, T::Conflict> {
76        // this drops the old mutable, causing all previous writes to be applied.
77        *self = Mutable {
78            cell: OnceCell::new(),
79            row_id: self.row_id,
80            _txn: PhantomData,
81        };
82        // we need to catch panics so that we can restore `self` to a valid state.
83        // if we don't do this then the Drop impl is likely to panic.
84        let res = std::panic::catch_unwind(AssertUnwindSafe(|| f(T::mutable_as_unique(self))));
85        // taking `self.cell` puts the Mutable in a guaranteed valid state.
86        // it doesn't matter if the update succeeds or not as long as we only deref after the update.
87        let update = T::mutable_into_update(self.cell.take().unwrap().val);
88        let out = match res {
89            Ok(out) => out,
90            Err(payload) => std::panic::resume_unwind(payload),
91        };
92        // only apply the update if there was no panic
93        #[expect(deprecated)]
94        Transaction::new_ref().update(self.row_id, update)?;
95
96        Ok(out)
97    }
98}
99
100impl<'transaction, T: Table> Deref for Mutable<'transaction, T> {
101    type Target = T::Mutable;
102
103    fn deref(&self) -> &Self::Target {
104        &self.cell.get_or_init(|| MutableInner::new(self.row_id)).val
105    }
106}
107
108impl<'transaction, T: Table> DerefMut for Mutable<'transaction, T> {
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        // initialize the cell
111        let _ = Mutable::deref(self);
112        let inner = self.cell.get_mut().unwrap();
113        inner.any_update = true;
114        &mut inner.val
115    }
116}
117
118impl<'transaction, T: Table> Drop for Mutable<'transaction, T> {
119    fn drop(&mut self) {
120        let Some(cell) = self.cell.take() else {
121            return;
122        };
123        if cell.any_update {
124            let update = T::mutable_into_update(cell.val);
125            #[expect(deprecated)]
126            let Ok(_) = Transaction::new_ref().update(self.row_id, update) else {
127                panic!("mutable can not fail, no unique is updated")
128            };
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135
136    use crate::{Database, migration::Config};
137
138    #[test]
139    fn mutable_shenanigans() {
140        #[crate::migration::schema(Test)]
141        pub mod vN {
142            pub struct Foo {
143                pub alpha: i64,
144                #[unique]
145                pub bravo: i64,
146            }
147        }
148        use v0::*;
149
150        let err = std::panic::catch_unwind(move || {
151            let db = Database::new(Config::open_in_memory());
152            db.transaction_mut_ok(|txn| {
153                txn.insert(Foo { alpha: 1, bravo: 1 }).unwrap();
154                let row = txn.insert(Foo { alpha: 1, bravo: 2 }).unwrap();
155                let mut mutable = txn.mutable(row);
156                mutable.alpha = 100;
157                mutable
158                    .unique(|x| {
159                        x.bravo = 1;
160                    })
161                    .unwrap_err();
162                assert_eq!(mutable.alpha, 100);
163                assert_eq!(mutable.bravo, 2);
164
165                let row = mutable.into_table_row();
166                let view = txn.lazy(row);
167                assert_eq!(view.alpha, 100);
168                assert_eq!(view.bravo, 2);
169
170                let mut mutable = txn.mutable(row);
171                mutable.alpha = 200;
172                mutable
173                    .unique(|x| {
174                        x.bravo = 1;
175                        panic!("error in unique")
176                    })
177                    .unwrap();
178            });
179        })
180        .unwrap_err();
181        assert_eq!(*err.downcast_ref::<&str>().unwrap(), "error in unique");
182    }
183}