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;
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_eq!(savepoint.version, FILE_FORMAT_VERSION2);
119        let mut result = vec![savepoint.version];
120        result.extend(savepoint.id.0.to_le_bytes());
121        result.extend(savepoint.transaction_id.raw_id().to_le_bytes());
122
123        if let Some(header) = savepoint.user_root {
124            result.push(1);
125            result.extend(header.to_le_bytes());
126        } else {
127            result.push(0);
128            result.extend([0; BtreeHeader::serialized_size()]);
129        }
130
131        if let Some(header) = savepoint.system_root {
132            result.push(1);
133            result.extend(header.to_le_bytes());
134        } else {
135            result.push(0);
136            result.extend([0; BtreeHeader::serialized_size()]);
137        }
138
139        if let Some(header) = savepoint.freed_root {
140            result.push(1);
141            result.extend(header.to_le_bytes());
142        } else {
143            result.push(0);
144            result.extend([0; BtreeHeader::serialized_size()]);
145        }
146
147        result.extend(
148            u32::try_from(savepoint.regional_allocators.len())
149                .unwrap()
150                .to_le_bytes(),
151        );
152        for region in &savepoint.regional_allocators {
153            assert_eq!(savepoint.regional_allocators[0].len(), region.len());
154        }
155        result.extend(
156            u32::try_from(savepoint.regional_allocators[0].len())
157                .unwrap()
158                .to_le_bytes(),
159        );
160
161        // TODO: this seems like it is going to fail on databases with > 3GiB of regional allocators
162        // since that is the max size of a single stored value
163        for region in &savepoint.regional_allocators {
164            result.extend(region);
165        }
166        Self::Owned(result)
167    }
168
169    fn data(&self) -> &[u8] {
170        match self {
171            SerializedSavepoint::Ref(x) => x,
172            SerializedSavepoint::Owned(x) => x.as_slice(),
173        }
174    }
175
176    pub(crate) fn to_savepoint(&self, transaction_tracker: Arc<TransactionTracker>) -> Savepoint {
177        let data = self.data();
178        let mut offset = 0;
179        let version = data[offset];
180        assert_eq!(version, FILE_FORMAT_VERSION2);
181        offset += size_of::<u8>();
182
183        let id = u64::from_le_bytes(
184            data[offset..(offset + size_of::<u64>())]
185                .try_into()
186                .unwrap(),
187        );
188        offset += size_of::<u64>();
189
190        let transaction_id = u64::from_le_bytes(
191            data[offset..(offset + size_of::<u64>())]
192                .try_into()
193                .unwrap(),
194        );
195        offset += size_of::<u64>();
196
197        let not_null = data[offset];
198        assert!(not_null == 0 || not_null == 1);
199        offset += 1;
200        let user_root = if not_null == 1 {
201            Some(BtreeHeader::from_le_bytes(
202                data[offset..(offset + BtreeHeader::serialized_size())]
203                    .try_into()
204                    .unwrap(),
205            ))
206        } else {
207            None
208        };
209        offset += BtreeHeader::serialized_size();
210
211        let not_null = data[offset];
212        assert!(not_null == 0 || not_null == 1);
213        offset += 1;
214        let system_root = if not_null == 1 {
215            Some(BtreeHeader::from_le_bytes(
216                data[offset..(offset + BtreeHeader::serialized_size())]
217                    .try_into()
218                    .unwrap(),
219            ))
220        } else {
221            None
222        };
223        offset += BtreeHeader::serialized_size();
224
225        let not_null = data[offset];
226        assert!(not_null == 0 || not_null == 1);
227        offset += 1;
228        let freed_root = if not_null == 1 {
229            Some(BtreeHeader::from_le_bytes(
230                data[offset..(offset + BtreeHeader::serialized_size())]
231                    .try_into()
232                    .unwrap(),
233            ))
234        } else {
235            None
236        };
237        offset += BtreeHeader::serialized_size();
238
239        let regions = u32::from_le_bytes(
240            data[offset..(offset + size_of::<u32>())]
241                .try_into()
242                .unwrap(),
243        ) as usize;
244        offset += size_of::<u32>();
245        let allocator_len = u32::from_le_bytes(
246            data[offset..(offset + size_of::<u32>())]
247                .try_into()
248                .unwrap(),
249        ) as usize;
250        offset += size_of::<u32>();
251
252        let mut regional_allocators = vec![];
253
254        for _ in 0..regions {
255            regional_allocators.push(data[offset..(offset + allocator_len)].to_vec());
256            offset += allocator_len;
257        }
258
259        assert_eq!(offset, data.len());
260
261        Savepoint {
262            version,
263            id: SavepointId(id),
264            transaction_id: TransactionId::new(transaction_id),
265            user_root,
266            system_root,
267            freed_root,
268            regional_allocators,
269            transaction_tracker,
270            ephemeral: false,
271        }
272    }
273}
274
275impl Value for SerializedSavepoint<'_> {
276    type SelfType<'a>
277        = SerializedSavepoint<'a>
278    where
279        Self: 'a;
280    type AsBytes<'a>
281        = &'a [u8]
282    where
283        Self: 'a;
284
285    fn fixed_width() -> Option<usize> {
286        None
287    }
288
289    fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
290    where
291        Self: 'a,
292    {
293        SerializedSavepoint::Ref(data)
294    }
295
296    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
297    where
298        Self: 'b,
299    {
300        value.data()
301    }
302
303    fn type_name() -> TypeName {
304        TypeName::internal("redb::SerializedSavepoint")
305    }
306}