Skip to main content

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        let select = Transaction::new_ref().query_one(T::into_select(row_id.into_expr()));
31        Self {
32            val: T::select_mutable(select),
33            any_update: false,
34        }
35    }
36}
37
38impl<'transaction, T: Table> Mutable<'transaction, T> {
39    pub(crate) fn new(inner: T::Mutable, row_id: TableRow<T>) -> Self {
40        Self {
41            cell: OnceCell::from(MutableInner {
42                val: inner,
43                any_update: false,
44            }),
45            row_id,
46            _txn: PhantomData,
47        }
48    }
49
50    /// Turn the [Mutable] into a [TableRow].
51    ///
52    /// This will end the lifetime of the [Mutable], which is useful since
53    /// [Mutable] does not have a non lexical lifetime, because of the [Drop] impl.
54    ///
55    /// If you do not need the [TableRow], then it is also possible to just call [drop].
56    pub fn into_table_row(self) -> TableRow<T> {
57        self.row_id
58    }
59
60    /// Update unique constraint columns.
61    ///
62    /// When the update succeeds, this function returns [Ok], when it fails it returns [Err] with one of
63    /// three conflict types:
64    /// - 0 unique constraints => [std::convert::Infallible]
65    /// - 1 unique constraint => [TableRow] reference to the conflicting table row.
66    /// - 2+ unique constraints => [crate::Conflict]
67    ///
68    /// If any of the changes made inside the closure conflict with an existing row, then all changes
69    /// made inside the closure are reverted.
70    ///
71    /// If the closure panics, then all changes made inside the closure are also reverted.
72    /// Applying those changes is not possible, as conflicts can not be reported if there is a panic.
73    pub fn unique<O>(
74        &mut self,
75        f: impl FnOnce(&mut <T::Mutable as Deref>::Target) -> O,
76    ) -> Result<O, T::Conflict> {
77        // this drops the old mutable, causing all previous writes to be applied.
78        *self = Mutable {
79            cell: OnceCell::new(),
80            row_id: self.row_id,
81            _txn: PhantomData,
82        };
83        // we need to catch panics so that we can restore `self` to a valid state.
84        // if we don't do this then the Drop impl is likely to panic.
85        let res = std::panic::catch_unwind(AssertUnwindSafe(|| f(T::mutable_as_unique(self))));
86        // taking `self.cell` puts the Mutable in a guaranteed valid state.
87        // it doesn't matter if the update succeeds or not as long as we only deref after the update.
88        let update = self.cell.take().unwrap().val;
89        let out = match res {
90            Ok(out) => out,
91            Err(payload) => std::panic::resume_unwind(payload),
92        };
93        // only apply the update if there was no panic
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 = cell.val;
125            let Ok(_) = Transaction::new_ref().update(self.row_id, update) else {
126                panic!("mutable can not fail, no unique is updated")
127            };
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134
135    use crate::{Database, migration::Config};
136
137    #[test]
138    fn mutable_shenanigans() {
139        #[crate::migration::schema(Test)]
140        pub mod vN {
141            pub struct Foo {
142                pub alpha: i64,
143                #[unique]
144                pub bravo: i64,
145            }
146        }
147        use v0::*;
148
149        let err = std::panic::catch_unwind(move || {
150            let db = Database::new(Config::open_in_memory());
151            db.transaction_mut_ok(|txn| {
152                txn.insert(Foo { alpha: 1, bravo: 1 }).unwrap();
153                let row = txn.insert(Foo { alpha: 1, bravo: 2 }).unwrap();
154                let mut mutable = txn.mutable(row);
155                mutable.alpha = 100;
156                mutable
157                    .unique(|x| {
158                        x.bravo = 1;
159                    })
160                    .unwrap_err();
161                assert_eq!(mutable.alpha, 100);
162                assert_eq!(mutable.bravo, 2);
163
164                let row = mutable.into_table_row();
165                let view = txn.lazy(row);
166                assert_eq!(view.alpha, 100);
167                assert_eq!(view.bravo, 2);
168
169                let mut mutable = txn.mutable(row);
170                mutable.alpha = 200;
171                mutable
172                    .unique(|x| {
173                        x.bravo = 1;
174                        panic!("error in unique")
175                    })
176                    .unwrap();
177            });
178        })
179        .unwrap_err();
180        assert_eq!(*err.downcast_ref::<&str>().unwrap(), "error in unique");
181    }
182}