redb_32bit/tree_store/page_store/
savepoint.rs

1use crate::transaction_tracker::{SavepointId, TransactionId, TransactionTracker};
2use crate::tree_store::{Checksum, PageNumber, TransactionalMemory};
3use crate::{RedbValue, TypeName};
4use std::fmt::Debug;
5use std::mem::size_of;
6use std::sync::{Arc, Mutex};
7
8// on-disk format:
9// * 1 byte: version
10// * 8 bytes: savepoint id
11// * 8 bytes: transaction id
12// * 1 byte: user root not-null
13// * 8 bytes: user root page
14// * 8 bytes: user root checksum
15// * 1 byte: system root not-null
16// * 8 bytes: system root page
17// * 8 bytes: system root checksum
18// * 1 byte: freed root not-null
19// * 8 bytes: freed root page
20// * 8 bytes: freed root checksum
21// * 4 bytes: number of regions
22// * 4 bytes: length of each regional tracker
23// * n = (number of regions * length of each) bytes: regional tracker data
24pub struct Savepoint {
25    version: u8,
26    id: SavepointId,
27    // Each savepoint has an associated read transaction id to ensure that any pages it references
28    // are not freed
29    transaction_id: TransactionId,
30    user_root: Option<(PageNumber, Checksum)>,
31    // For future use. This is not used in the restoration protocol.
32    system_root: Option<(PageNumber, Checksum)>,
33    freed_root: Option<(PageNumber, Checksum)>,
34    regional_allocators: Vec<Vec<u8>>,
35    transaction_tracker: Arc<Mutex<TransactionTracker>>,
36    ephemeral: bool,
37}
38
39impl Savepoint {
40    #[allow(clippy::too_many_arguments)]
41    pub(crate) fn new_ephemeral(
42        mem: &TransactionalMemory,
43        transaction_tracker: Arc<Mutex<TransactionTracker>>,
44        id: SavepointId,
45        transaction_id: TransactionId,
46        user_root: Option<(PageNumber, Checksum)>,
47        system_root: Option<(PageNumber, Checksum)>,
48        freed_root: Option<(PageNumber, Checksum)>,
49        regional_allocators: Vec<Vec<u8>>,
50    ) -> Self {
51        Self {
52            id,
53            transaction_id,
54            version: mem.get_version(),
55            user_root,
56            system_root,
57            freed_root,
58            regional_allocators,
59            transaction_tracker,
60            ephemeral: true,
61        }
62    }
63
64    pub(crate) fn get_version(&self) -> u8 {
65        self.version
66    }
67
68    pub(crate) fn get_id(&self) -> SavepointId {
69        self.id
70    }
71
72    pub(crate) fn get_transaction_id(&self) -> TransactionId {
73        self.transaction_id
74    }
75
76    pub(crate) fn get_user_root(&self) -> Option<(PageNumber, Checksum)> {
77        self.user_root
78    }
79
80    pub(crate) fn get_freed_root(&self) -> Option<(PageNumber, Checksum)> {
81        self.freed_root
82    }
83
84    pub(crate) fn get_regional_allocator_states(&self) -> &[Vec<u8>] {
85        &self.regional_allocators
86    }
87
88    pub(crate) fn db_address(&self) -> *const Mutex<TransactionTracker> {
89        self.transaction_tracker.as_ref() as *const _
90    }
91
92    pub(crate) fn set_persistent(&mut self) {
93        self.ephemeral = false;
94    }
95}
96
97impl Drop for Savepoint {
98    fn drop(&mut self) {
99        if self.ephemeral {
100            self.transaction_tracker
101                .lock()
102                .unwrap()
103                .deallocate_savepoint(self.get_id(), self.get_transaction_id());
104        }
105    }
106}
107
108#[derive(Debug)]
109pub(crate) enum SerializedSavepoint<'a> {
110    Ref(&'a [u8]),
111    Owned(Vec<u8>),
112}
113
114impl<'a> SerializedSavepoint<'a> {
115    pub(crate) fn from_savepoint(savepoint: &Savepoint) -> Self {
116        let mut result = vec![savepoint.version];
117        result.extend(savepoint.id.0.to_le_bytes());
118        result.extend(savepoint.transaction_id.raw_id().to_le_bytes());
119
120        if let Some((root, checksum)) = savepoint.user_root {
121            result.push(1);
122            result.extend(root.to_le_bytes());
123            result.extend(checksum.to_le_bytes());
124        } else {
125            result.push(0);
126            result.extend([0; PageNumber::serialized_size()]);
127            result.extend((0 as Checksum).to_le_bytes());
128        }
129
130        if let Some((root, checksum)) = savepoint.system_root {
131            result.push(1);
132            result.extend(root.to_le_bytes());
133            result.extend(checksum.to_le_bytes());
134        } else {
135            result.push(0);
136            result.extend([0; PageNumber::serialized_size()]);
137            result.extend((0 as Checksum).to_le_bytes());
138        }
139
140        if let Some((root, checksum)) = savepoint.freed_root {
141            result.push(1);
142            result.extend(root.to_le_bytes());
143            result.extend(checksum.to_le_bytes());
144        } else {
145            result.push(0);
146            result.extend([0; PageNumber::serialized_size()]);
147            result.extend((0 as Checksum).to_le_bytes());
148        }
149
150        result.extend(
151            u32::try_from(savepoint.regional_allocators.len())
152                .unwrap()
153                .to_le_bytes(),
154        );
155        for region in savepoint.regional_allocators.iter() {
156            assert_eq!(savepoint.regional_allocators[0].len(), region.len());
157        }
158        result.extend(
159            u32::try_from(savepoint.regional_allocators[0].len())
160                .unwrap()
161                .to_le_bytes(),
162        );
163
164        for region in savepoint.regional_allocators.iter() {
165            result.extend(region);
166        }
167        Self::Owned(result)
168    }
169
170    fn data(&self) -> &[u8] {
171        match self {
172            SerializedSavepoint::Ref(x) => x,
173            SerializedSavepoint::Owned(x) => x.as_slice(),
174        }
175    }
176
177    pub(crate) fn to_savepoint(
178        &self,
179        transaction_tracker: Arc<Mutex<TransactionTracker>>,
180    ) -> Savepoint {
181        let data = self.data();
182        let mut offset = 0;
183        let version = data[offset];
184        offset += size_of::<u8>();
185
186        let id = u64::from_le_bytes(
187            data[offset..(offset + size_of::<u64>())]
188                .try_into()
189                .unwrap(),
190        );
191        offset += size_of::<u64>();
192
193        let transaction_id = u64::from_le_bytes(
194            data[offset..(offset + size_of::<u64>())]
195                .try_into()
196                .unwrap(),
197        );
198        offset += size_of::<u64>();
199
200        let not_null = data[offset];
201        assert!(not_null == 0 || not_null == 1);
202        offset += 1;
203        let user_root = if not_null == 1 {
204            let page_number = PageNumber::from_le_bytes(
205                data[offset..(offset + PageNumber::serialized_size())]
206                    .try_into()
207                    .unwrap(),
208            );
209            offset += PageNumber::serialized_size();
210            let checksum = Checksum::from_le_bytes(
211                data[offset..(offset + size_of::<Checksum>())]
212                    .try_into()
213                    .unwrap(),
214            );
215            offset += size_of::<Checksum>();
216            Some((page_number, checksum))
217        } else {
218            offset += PageNumber::serialized_size();
219            offset += size_of::<Checksum>();
220            None
221        };
222
223        let not_null = data[offset];
224        assert!(not_null == 0 || not_null == 1);
225        offset += 1;
226        let system_root = if not_null == 1 {
227            let page_number = PageNumber::from_le_bytes(
228                data[offset..(offset + PageNumber::serialized_size())]
229                    .try_into()
230                    .unwrap(),
231            );
232            offset += PageNumber::serialized_size();
233            let checksum = Checksum::from_le_bytes(
234                data[offset..(offset + size_of::<Checksum>())]
235                    .try_into()
236                    .unwrap(),
237            );
238            offset += size_of::<Checksum>();
239            Some((page_number, checksum))
240        } else {
241            offset += PageNumber::serialized_size();
242            offset += size_of::<Checksum>();
243            None
244        };
245
246        let not_null = data[offset];
247        assert!(not_null == 0 || not_null == 1);
248        offset += 1;
249        let freed_root = if not_null == 1 {
250            let page_number = PageNumber::from_le_bytes(
251                data[offset..(offset + PageNumber::serialized_size())]
252                    .try_into()
253                    .unwrap(),
254            );
255            offset += PageNumber::serialized_size();
256            let checksum = Checksum::from_le_bytes(
257                data[offset..(offset + size_of::<Checksum>())]
258                    .try_into()
259                    .unwrap(),
260            );
261            offset += size_of::<Checksum>();
262            Some((page_number, checksum))
263        } else {
264            offset += PageNumber::serialized_size();
265            offset += size_of::<Checksum>();
266            None
267        };
268
269        let regions = u32::from_le_bytes(
270            data[offset..(offset + size_of::<u32>())]
271                .try_into()
272                .unwrap(),
273        ) as usize;
274        offset += size_of::<u32>();
275        let allocator_len = u32::from_le_bytes(
276            data[offset..(offset + size_of::<u32>())]
277                .try_into()
278                .unwrap(),
279        ) as usize;
280        offset += size_of::<u32>();
281
282        let mut regional_allocators = vec![];
283
284        for _ in 0..regions {
285            regional_allocators.push(data[offset..(offset + allocator_len)].to_vec());
286            offset += allocator_len;
287        }
288
289        assert_eq!(offset, data.len());
290
291        Savepoint {
292            version,
293            id: SavepointId(id),
294            transaction_id: TransactionId::new(transaction_id),
295            user_root,
296            system_root,
297            freed_root,
298            regional_allocators,
299            transaction_tracker,
300            ephemeral: false,
301        }
302    }
303}
304
305impl<'data> RedbValue for SerializedSavepoint<'data> {
306    type SelfType<'a> = SerializedSavepoint<'a> where Self: 'a;
307    type AsBytes<'a> = &'a [u8] where Self: 'a;
308
309    fn fixed_width() -> Option<usize> {
310        None
311    }
312
313    fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
314    where
315        Self: 'a,
316    {
317        SerializedSavepoint::Ref(data)
318    }
319
320    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
321    where
322        Self: 'a,
323        Self: 'b,
324    {
325        value.data()
326    }
327
328    fn type_name() -> TypeName {
329        TypeName::internal("redb::SerializedSavepoint")
330    }
331}