1use std::{
2 cell::OnceCell,
3 marker::PhantomData,
4 ops::{Deref, DerefMut},
5 panic::AssertUnwindSafe,
6};
7
8use crate::{IntoExpr, Table, TableRow, Transaction};
9
10pub 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 pub fn into_table_row(self) -> TableRow<T> {
59 self.row_id
60 }
61
62 pub fn unique<O>(
76 &mut self,
77 f: impl FnOnce(&mut <T::Mutable as Deref>::Target) -> O,
78 ) -> Result<O, T::Conflict> {
79 *self = Mutable {
81 cell: OnceCell::new(),
82 row_id: self.row_id,
83 _txn: PhantomData,
84 };
85 let res = std::panic::catch_unwind(AssertUnwindSafe(|| f(T::mutable_as_unique(self))));
88 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 #[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 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}