rust_query/scoped_transaction.rs
1use std::{
2 any::Any,
3 cell::{Cell, OnceCell},
4 marker::PhantomData,
5 ops::{Deref, DerefMut},
6};
7
8use crate::{
9 IntoExpr, Mutable, Table, TableRow, Transaction, private::IntoJoinable,
10 transaction::try_update_private, value::OptTable,
11};
12
13/// [Transaction] with mutation support and without downgrade support.
14///
15/// This type can be created using [Transaction::scope].
16pub struct TransactionScope<S: 'static> {
17 pub(crate) _p2: PhantomData<&'static Transaction<S>>,
18 pub(crate) tmp: Cell<Vec<Box<dyn Any>>>,
19}
20
21impl<S> DerefMut for TransactionScope<S> {
22 fn deref_mut(&mut self) -> &mut Self::Target {
23 self.tmp.take();
24 Transaction::new_ref()
25 }
26}
27
28impl<S> Deref for TransactionScope<S> {
29 type Target = Transaction<S>;
30
31 fn deref(&self) -> &Self::Target {
32 self.tmp.take();
33 Transaction::new_ref()
34 }
35}
36
37impl<S> TransactionScope<S> {
38 /// Retrieves a [Mutable] or `Option<Mutable>` from the database.
39 ///
40 /// The [Transaction] is borrowed mutably until the [Mutable] is dropped.
41 ///
42 /// ```
43 /// # #[rust_query::migration::schema(M)]
44 /// # pub mod vN {
45 /// # pub struct Player {
46 /// # #[unique]
47 /// # pub number: i64,
48 /// # pub name: String,
49 /// # pub score: i64,
50 /// # }
51 /// # }
52 /// # use v0::*;
53 /// # rust_query::Database::new(rust_query::migration::Config::open_in_memory()).transaction_mut_ok(|txn| {
54 /// txn.scope(|txn| {
55 /// let baz_id = txn.insert(Player {number: 1, name: "Baz".to_owned(), score: 0}).unwrap();
56 ///
57 /// let mut tmp = txn.mutable(baz_id);
58 /// tmp.score += 50;
59 /// tmp.name = format!("{}{}", tmp.name, tmp.score);
60 ///
61 /// if let Some(mut player) = txn.mutable(Player.number(1)) {
62 /// player.score += 100;
63 /// }
64 /// # })});
65 /// ```
66 pub fn mutable<'t, T: OptTable<Schema = S>>(
67 &'t mut self,
68 val: impl IntoExpr<'static, S, Typ = T>,
69 ) -> T::Mutable<'t> {
70 let x = self.query_one(val.into_expr());
71 T::into_mutable(self, x)
72 }
73
74 /// Retrieve multiple [Mutable] rows from the database.
75 ///
76 /// Refer to [crate::args::Rows::join] for the kind of the parameter that is supported here.
77 /// This may be useful when you need mutable access to multiple rows (potentially at the same time).
78 ///
79 /// Getting a lazy [Iterator] over mutable rows instead of a [Vec] is not possible, because mutating
80 /// while iterating can result in duplicate rows.
81 ///
82 /// ```
83 /// # #[rust_query::migration::schema(M)]
84 /// # pub mod vN {
85 /// # #[index(age)]
86 /// # pub struct User { pub age: i64 }
87 /// # }
88 /// # use v0::*;
89 /// # rust_query::Database::new(rust_query::migration::Config::open_in_memory()).transaction_mut_ok(|mut txn| {
90 /// # txn.scope(|txn|{
91 /// # txn.insert_ok(User {age: 30});
92 /// for mut user in txn.mutable_vec(User.age(20)) {
93 /// user.age += 1;
94 /// }
95 /// # })});
96 /// ```
97 pub fn mutable_vec<'t, T: Table<Schema = S>>(
98 &'t mut self,
99 val: impl IntoJoinable<'static, S, Typ = TableRow<T>>,
100 ) -> Vec<Mutable<'t, T>> {
101 let val = val.into_joinable();
102
103 let new_mutable = self.query(|rows| {
104 let val = rows.join(val);
105 rows.into_iter(val).map(|x| MutTemp::new(x) as _).collect()
106 });
107 self.tmp = Cell::new(new_mutable);
108
109 Cell::get_mut(&mut self.tmp)
110 .iter_mut()
111 .map(|x| Mutable::new(&mut **x))
112 .collect()
113 }
114}
115
116pub struct MutTemp<T: Table> {
117 pub inner: OnceCell<T::Mutable>,
118 pub row_id: TableRow<T>,
119}
120
121impl<T: Table> MutTemp<T> {
122 pub fn new(row_id: TableRow<T>) -> Box<Self> {
123 Box::new(MutTemp {
124 inner: OnceCell::new(),
125 row_id,
126 })
127 }
128}
129
130impl<T: Table> Drop for MutTemp<T> {
131 fn drop(&mut self) {
132 if let Some(update) = self.inner.take() {
133 let Ok(_) = try_update_private(self.row_id, update) else {
134 panic!("mutable can not fail, no unique is updated")
135 };
136 }
137 }
138}