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