redb/tree_store/page_store/
savepoint.rs

1use crate::transaction_tracker::{SavepointId, TransactionId, TransactionTracker};
2use crate::tree_store::page_store::page_manager::{FILE_FORMAT_VERSION2, FILE_FORMAT_VERSION3};
3use crate::tree_store::{BtreeHeader, TransactionalMemory};
4use crate::{TypeName, Value};
5use std::fmt::Debug;
6use std::mem::size_of;
7use std::sync::Arc;
8
9// on-disk format:
10// * 1 byte: version
11// * 8 bytes: savepoint id
12// * 8 bytes: transaction id
13// * 1 byte: user root not-null
14// * 8 bytes: user root page
15// * 8 bytes: user root checksum
16// * 1 byte: system root not-null
17// * 8 bytes: system root page
18// * 8 bytes: system root checksum
19// * 1 byte: freed root not-null
20// * 8 bytes: freed root page
21// * 8 bytes: freed root checksum
22// * 4 bytes: number of regions
23// * 4 bytes: length of each regional tracker
24// * n = (number of regions * length of each) bytes: regional tracker data
25pub struct Savepoint {
26    version: u8,
27    id: SavepointId,
28    // Each savepoint has an associated read transaction id to ensure that any pages it references
29    // are not freed
30    transaction_id: TransactionId,
31    user_root: Option<BtreeHeader>,
32    system_root: Option<BtreeHeader>,
33    freed_root: Option<BtreeHeader>,
34    regional_allocators: Vec<Vec<u8>>,
35    transaction_tracker: Arc<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<TransactionTracker>,
44        id: SavepointId,
45        transaction_id: TransactionId,
46        user_root: Option<BtreeHeader>,
47        system_root: Option<BtreeHeader>,
48        freed_root: Option<BtreeHeader>,
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<BtreeHeader> {
77        self.user_root
78    }
79
80    pub(crate) fn get_system_root(&self) -> Option<BtreeHeader> {
81        self.system_root
82    }
83
84    pub(crate) fn get_freed_root(&self) -> Option<BtreeHeader> {
85        self.freed_root
86    }
87
88    pub(crate) fn get_regional_allocators(&self) -> &Vec<Vec<u8>> {
89        &self.regional_allocators
90    }
91
92    pub(crate) fn db_address(&self) -> *const TransactionTracker {
93        std::ptr::from_ref(self.transaction_tracker.as_ref())
94    }
95
96    pub(crate) fn set_persistent(&mut self) {
97        self.ephemeral = false;
98    }
99}
100
101impl Drop for Savepoint {
102    fn drop(&mut self) {
103        if self.ephemeral {
104            self.transaction_tracker
105                .deallocate_savepoint(self.get_id(), self.get_transaction_id());
106        }
107    }
108}
109
110#[derive(Debug)]
111pub(crate) enum SerializedSavepoint<'a> {
112    Ref(&'a [u8]),
113    Owned(Vec<u8>),
114}
115
116impl SerializedSavepoint<'_> {
117    pub(crate) fn from_savepoint(savepoint: &Savepoint) -> Self {
118        assert!(
119            savepoint.version == FILE_FORMAT_VERSION2 || savepoint.version == FILE_FORMAT_VERSION3
120        );
121        let mut result = vec![savepoint.version];
122        result.extend(savepoint.id.0.to_le_bytes());
123        result.extend(savepoint.transaction_id.raw_id().to_le_bytes());
124
125        if let Some(header) = savepoint.user_root {
126            result.push(1);
127            result.extend(header.to_le_bytes());
128        } else {
129            result.push(0);
130            result.extend([0; BtreeHeader::serialized_size()]);
131        }
132
133        if savepoint.version <= FILE_FORMAT_VERSION2 {
134            if let Some(header) = savepoint.system_root {
135                result.push(1);
136                result.extend(header.to_le_bytes());
137            } else {
138                result.push(0);
139                result.extend([0; BtreeHeader::serialized_size()]);
140            }
141
142            if let Some(header) = savepoint.freed_root {
143                result.push(1);
144                result.extend(header.to_le_bytes());
145            } else {
146                result.push(0);
147                result.extend([0; BtreeHeader::serialized_size()]);
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 {
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            // TODO: this seems like it is going to fail on databases with > 3GiB of regional allocators
165            // since that is the max size of a single stored value
166            for region in &savepoint.regional_allocators {
167                result.extend(region);
168            }
169        }
170
171        Self::Owned(result)
172    }
173
174    fn data(&self) -> &[u8] {
175        match self {
176            SerializedSavepoint::Ref(x) => x,
177            SerializedSavepoint::Owned(x) => x.as_slice(),
178        }
179    }
180
181    pub(crate) fn to_savepoint(&self, transaction_tracker: Arc<TransactionTracker>) -> Savepoint {
182        let data = self.data();
183        let mut offset = 0;
184        let version = data[offset];
185        assert!(version == FILE_FORMAT_VERSION2 || version == FILE_FORMAT_VERSION3);
186        offset += size_of::<u8>();
187
188        let id = u64::from_le_bytes(
189            data[offset..(offset + size_of::<u64>())]
190                .try_into()
191                .unwrap(),
192        );
193        offset += size_of::<u64>();
194
195        let transaction_id = u64::from_le_bytes(
196            data[offset..(offset + size_of::<u64>())]
197                .try_into()
198                .unwrap(),
199        );
200        offset += size_of::<u64>();
201
202        let not_null = data[offset];
203        assert!(not_null == 0 || not_null == 1);
204        offset += 1;
205        let user_root = if not_null == 1 {
206            Some(BtreeHeader::from_le_bytes(
207                data[offset..(offset + BtreeHeader::serialized_size())]
208                    .try_into()
209                    .unwrap(),
210            ))
211        } else {
212            None
213        };
214        offset += BtreeHeader::serialized_size();
215
216        let (system_root, freed_root, regional_allocators) = if version >= FILE_FORMAT_VERSION3 {
217            (None, None, vec![])
218        } else {
219            let not_null = data[offset];
220            assert!(not_null == 0 || not_null == 1);
221            offset += 1;
222            let system_root = if not_null == 1 {
223                Some(BtreeHeader::from_le_bytes(
224                    data[offset..(offset + BtreeHeader::serialized_size())]
225                        .try_into()
226                        .unwrap(),
227                ))
228            } else {
229                None
230            };
231            offset += BtreeHeader::serialized_size();
232
233            let not_null = data[offset];
234            assert!(not_null == 0 || not_null == 1);
235            offset += 1;
236            let freed_root = if not_null == 1 {
237                Some(BtreeHeader::from_le_bytes(
238                    data[offset..(offset + BtreeHeader::serialized_size())]
239                        .try_into()
240                        .unwrap(),
241                ))
242            } else {
243                None
244            };
245            offset += BtreeHeader::serialized_size();
246
247            let regions = u32::from_le_bytes(
248                data[offset..(offset + size_of::<u32>())]
249                    .try_into()
250                    .unwrap(),
251            ) as usize;
252            offset += size_of::<u32>();
253            let allocator_len = u32::from_le_bytes(
254                data[offset..(offset + size_of::<u32>())]
255                    .try_into()
256                    .unwrap(),
257            ) as usize;
258            offset += size_of::<u32>();
259
260            let mut regional_allocators = vec![];
261
262            for _ in 0..regions {
263                regional_allocators.push(data[offset..(offset + allocator_len)].to_vec());
264                offset += allocator_len;
265            }
266
267            (system_root, freed_root, regional_allocators)
268        };
269
270        assert_eq!(offset, data.len());
271
272        Savepoint {
273            version,
274            id: SavepointId(id),
275            transaction_id: TransactionId::new(transaction_id),
276            user_root,
277            system_root,
278            freed_root,
279            regional_allocators,
280            transaction_tracker,
281            ephemeral: false,
282        }
283    }
284}
285
286impl Value for SerializedSavepoint<'_> {
287    type SelfType<'a>
288        = SerializedSavepoint<'a>
289    where
290        Self: 'a;
291    type AsBytes<'a>
292        = &'a [u8]
293    where
294        Self: 'a;
295
296    fn fixed_width() -> Option<usize> {
297        None
298    }
299
300    fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
301    where
302        Self: 'a,
303    {
304        SerializedSavepoint::Ref(data)
305    }
306
307    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
308    where
309        Self: 'b,
310    {
311        value.data()
312    }
313
314    fn type_name() -> TypeName {
315        TypeName::internal("redb::SerializedSavepoint")
316    }
317}