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    // For future use. This is not used in the restoration protocol.
33    system_root: Option<BtreeHeader>,
34    freed_root: Option<BtreeHeader>,
35    regional_allocators: Vec<Vec<u8>>,
36    transaction_tracker: Arc<TransactionTracker>,
37    ephemeral: bool,
38}
39
40impl Savepoint {
41    #[allow(clippy::too_many_arguments)]
42    pub(crate) fn new_ephemeral(
43        mem: &TransactionalMemory,
44        transaction_tracker: Arc<TransactionTracker>,
45        id: SavepointId,
46        transaction_id: TransactionId,
47        user_root: Option<BtreeHeader>,
48        system_root: Option<BtreeHeader>,
49        freed_root: Option<BtreeHeader>,
50        regional_allocators: Vec<Vec<u8>>,
51    ) -> Self {
52        Self {
53            id,
54            transaction_id,
55            version: mem.get_version(),
56            user_root,
57            system_root,
58            freed_root,
59            regional_allocators,
60            transaction_tracker,
61            ephemeral: true,
62        }
63    }
64
65    pub(crate) fn get_version(&self) -> u8 {
66        self.version
67    }
68
69    pub(crate) fn get_id(&self) -> SavepointId {
70        self.id
71    }
72
73    pub(crate) fn get_transaction_id(&self) -> TransactionId {
74        self.transaction_id
75    }
76
77    pub(crate) fn get_user_root(&self) -> Option<BtreeHeader> {
78        self.user_root
79    }
80
81    pub(crate) fn db_address(&self) -> *const TransactionTracker {
82        std::ptr::from_ref(self.transaction_tracker.as_ref())
83    }
84
85    pub(crate) fn set_persistent(&mut self) {
86        self.ephemeral = false;
87    }
88}
89
90impl Drop for Savepoint {
91    fn drop(&mut self) {
92        if self.ephemeral {
93            self.transaction_tracker
94                .deallocate_savepoint(self.get_id(), self.get_transaction_id());
95        }
96    }
97}
98
99#[derive(Debug)]
100pub(crate) enum SerializedSavepoint<'a> {
101    Ref(&'a [u8]),
102    Owned(Vec<u8>),
103}
104
105impl<'a> SerializedSavepoint<'a> {
106    pub(crate) fn from_savepoint(savepoint: &Savepoint) -> Self {
107        assert_eq!(savepoint.version, FILE_FORMAT_VERSION2);
108        let mut result = vec![savepoint.version];
109        result.extend(savepoint.id.0.to_le_bytes());
110        result.extend(savepoint.transaction_id.raw_id().to_le_bytes());
111
112        if let Some(header) = savepoint.user_root {
113            result.push(1);
114            result.extend(header.to_le_bytes());
115        } else {
116            result.push(0);
117            result.extend([0; BtreeHeader::serialized_size()]);
118        }
119
120        if let Some(header) = savepoint.system_root {
121            result.push(1);
122            result.extend(header.to_le_bytes());
123        } else {
124            result.push(0);
125            result.extend([0; BtreeHeader::serialized_size()]);
126        }
127
128        if let Some(header) = savepoint.freed_root {
129            result.push(1);
130            result.extend(header.to_le_bytes());
131        } else {
132            result.push(0);
133            result.extend([0; BtreeHeader::serialized_size()]);
134        }
135
136        result.extend(
137            u32::try_from(savepoint.regional_allocators.len())
138                .unwrap()
139                .to_le_bytes(),
140        );
141        for region in &savepoint.regional_allocators {
142            assert_eq!(savepoint.regional_allocators[0].len(), region.len());
143        }
144        result.extend(
145            u32::try_from(savepoint.regional_allocators[0].len())
146                .unwrap()
147                .to_le_bytes(),
148        );
149
150        for region in &savepoint.regional_allocators {
151            result.extend(region);
152        }
153        Self::Owned(result)
154    }
155
156    fn data(&self) -> &[u8] {
157        match self {
158            SerializedSavepoint::Ref(x) => x,
159            SerializedSavepoint::Owned(x) => x.as_slice(),
160        }
161    }
162
163    pub(crate) fn to_savepoint(&self, transaction_tracker: Arc<TransactionTracker>) -> Savepoint {
164        let data = self.data();
165        let mut offset = 0;
166        let version = data[offset];
167        assert_eq!(version, FILE_FORMAT_VERSION2);
168        offset += size_of::<u8>();
169
170        let id = u64::from_le_bytes(
171            data[offset..(offset + size_of::<u64>())]
172                .try_into()
173                .unwrap(),
174        );
175        offset += size_of::<u64>();
176
177        let transaction_id = u64::from_le_bytes(
178            data[offset..(offset + size_of::<u64>())]
179                .try_into()
180                .unwrap(),
181        );
182        offset += size_of::<u64>();
183
184        let not_null = data[offset];
185        assert!(not_null == 0 || not_null == 1);
186        offset += 1;
187        let user_root = if not_null == 1 {
188            Some(BtreeHeader::from_le_bytes(
189                data[offset..(offset + BtreeHeader::serialized_size())]
190                    .try_into()
191                    .unwrap(),
192            ))
193        } else {
194            None
195        };
196        offset += BtreeHeader::serialized_size();
197
198        let not_null = data[offset];
199        assert!(not_null == 0 || not_null == 1);
200        offset += 1;
201        let system_root = if not_null == 1 {
202            Some(BtreeHeader::from_le_bytes(
203                data[offset..(offset + BtreeHeader::serialized_size())]
204                    .try_into()
205                    .unwrap(),
206            ))
207        } else {
208            None
209        };
210        offset += BtreeHeader::serialized_size();
211
212        let not_null = data[offset];
213        assert!(not_null == 0 || not_null == 1);
214        offset += 1;
215        let freed_root = if not_null == 1 {
216            Some(BtreeHeader::from_le_bytes(
217                data[offset..(offset + BtreeHeader::serialized_size())]
218                    .try_into()
219                    .unwrap(),
220            ))
221        } else {
222            None
223        };
224        offset += BtreeHeader::serialized_size();
225
226        let regions = u32::from_le_bytes(
227            data[offset..(offset + size_of::<u32>())]
228                .try_into()
229                .unwrap(),
230        ) as usize;
231        offset += size_of::<u32>();
232        let allocator_len = u32::from_le_bytes(
233            data[offset..(offset + size_of::<u32>())]
234                .try_into()
235                .unwrap(),
236        ) as usize;
237        offset += size_of::<u32>();
238
239        let mut regional_allocators = vec![];
240
241        for _ in 0..regions {
242            regional_allocators.push(data[offset..(offset + allocator_len)].to_vec());
243            offset += allocator_len;
244        }
245
246        assert_eq!(offset, data.len());
247
248        Savepoint {
249            version,
250            id: SavepointId(id),
251            transaction_id: TransactionId::new(transaction_id),
252            user_root,
253            system_root,
254            freed_root,
255            regional_allocators,
256            transaction_tracker,
257            ephemeral: false,
258        }
259    }
260}
261
262impl<'data> Value for SerializedSavepoint<'data> {
263    type SelfType<'a> = SerializedSavepoint<'a> where Self: 'a;
264    type AsBytes<'a> = &'a [u8] where Self: 'a;
265
266    fn fixed_width() -> Option<usize> {
267        None
268    }
269
270    fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
271    where
272        Self: 'a,
273    {
274        SerializedSavepoint::Ref(data)
275    }
276
277    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
278    where
279        Self: 'b,
280    {
281        value.data()
282    }
283
284    fn type_name() -> TypeName {
285        TypeName::internal("redb::SerializedSavepoint")
286    }
287}