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> {
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 pub fn into_table_row(self) -> TableRow<T> {
56 self.row_id
57 }
58
59 pub fn unique<O>(
73 &mut self,
74 f: impl FnOnce(&mut <T::Mutable as Deref>::Target) -> O,
75 ) -> Result<O, T::Conflict> {
76 *self = Mutable {
78 cell: OnceCell::new(),
79 row_id: self.row_id,
80 _txn: PhantomData,
81 };
82 let res = std::panic::catch_unwind(AssertUnwindSafe(|| f(T::mutable_as_unique(self))));
85 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 #[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 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}