manifold/tree_store/page_store/
savepoint.rs

1use crate::transaction_tracker::{SavepointId, TransactionId, TransactionTracker};
2use crate::tree_store::page_store::page_manager::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/// A database savepoint
17///
18/// May be used with [`WriteTransaction::restore_savepoint`] to restore the database to the state
19/// when this savepoint was created
20///
21/// [`WriteTransaction::restore_savepoint`]: crate::WriteTransaction::restore_savepoint
22pub struct Savepoint {
23    version: u8,
24    id: SavepointId,
25    // Each savepoint has an associated read transaction id to ensure that any pages it references
26    // are not freed
27    transaction_id: TransactionId,
28    user_root: Option<BtreeHeader>,
29    transaction_tracker: Arc<TransactionTracker>,
30    ephemeral: bool,
31}
32
33impl Savepoint {
34    #[allow(clippy::too_many_arguments)]
35    pub(crate) fn new_ephemeral(
36        mem: &TransactionalMemory,
37        transaction_tracker: Arc<TransactionTracker>,
38        id: SavepointId,
39        transaction_id: TransactionId,
40        user_root: Option<BtreeHeader>,
41    ) -> Self {
42        Self {
43            id,
44            transaction_id,
45            version: mem.get_version(),
46            user_root,
47            transaction_tracker,
48            ephemeral: true,
49        }
50    }
51
52    pub(crate) fn get_version(&self) -> u8 {
53        self.version
54    }
55
56    pub(crate) fn get_id(&self) -> SavepointId {
57        self.id
58    }
59
60    pub(crate) fn get_transaction_id(&self) -> TransactionId {
61        self.transaction_id
62    }
63
64    pub(crate) fn get_user_root(&self) -> Option<BtreeHeader> {
65        self.user_root
66    }
67
68    pub(crate) fn db_address(&self) -> *const TransactionTracker {
69        std::ptr::from_ref(self.transaction_tracker.as_ref())
70    }
71
72    pub(crate) fn set_persistent(&mut self) {
73        self.ephemeral = false;
74    }
75}
76
77impl Drop for Savepoint {
78    fn drop(&mut self) {
79        if self.ephemeral {
80            self.transaction_tracker
81                .deallocate_savepoint(self.get_id(), self.get_transaction_id());
82        }
83    }
84}
85
86#[derive(Debug)]
87pub(crate) enum SerializedSavepoint<'a> {
88    Ref(&'a [u8]),
89    Owned(Vec<u8>),
90}
91
92impl SerializedSavepoint<'_> {
93    pub(crate) fn from_savepoint(savepoint: &Savepoint) -> Self {
94        assert_eq!(savepoint.version, FILE_FORMAT_VERSION3);
95        let mut result = vec![savepoint.version];
96        result.extend(savepoint.id.0.to_le_bytes());
97        result.extend(savepoint.transaction_id.raw_id().to_le_bytes());
98
99        if let Some(header) = savepoint.user_root {
100            result.push(1);
101            result.extend(header.to_le_bytes());
102        } else {
103            result.push(0);
104            result.extend([0; BtreeHeader::serialized_size()]);
105        }
106
107        Self::Owned(result)
108    }
109
110    fn data(&self) -> &[u8] {
111        match self {
112            SerializedSavepoint::Ref(x) => x,
113            SerializedSavepoint::Owned(x) => x.as_slice(),
114        }
115    }
116
117    pub(crate) fn to_savepoint(&self, transaction_tracker: Arc<TransactionTracker>) -> Savepoint {
118        let data = self.data();
119        let mut offset = 0;
120        let version = data[offset];
121        assert_eq!(version, FILE_FORMAT_VERSION3);
122        offset += size_of::<u8>();
123
124        let id = u64::from_le_bytes(
125            data[offset..(offset + size_of::<u64>())]
126                .try_into()
127                .unwrap(),
128        );
129        offset += size_of::<u64>();
130
131        let transaction_id = u64::from_le_bytes(
132            data[offset..(offset + size_of::<u64>())]
133                .try_into()
134                .unwrap(),
135        );
136        offset += size_of::<u64>();
137
138        let not_null = data[offset];
139        assert!(not_null == 0 || not_null == 1);
140        offset += 1;
141        let user_root = if not_null == 1 {
142            Some(BtreeHeader::from_le_bytes(
143                data[offset..(offset + BtreeHeader::serialized_size())]
144                    .try_into()
145                    .unwrap(),
146            ))
147        } else {
148            None
149        };
150        offset += BtreeHeader::serialized_size();
151        assert_eq!(offset, data.len());
152
153        Savepoint {
154            version,
155            id: SavepointId(id),
156            transaction_id: TransactionId::new(transaction_id),
157            user_root,
158            transaction_tracker,
159            ephemeral: false,
160        }
161    }
162}
163
164impl Value for SerializedSavepoint<'_> {
165    type SelfType<'a>
166        = SerializedSavepoint<'a>
167    where
168        Self: 'a;
169    type AsBytes<'a>
170        = &'a [u8]
171    where
172        Self: 'a;
173
174    fn fixed_width() -> Option<usize> {
175        None
176    }
177
178    fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
179    where
180        Self: 'a,
181    {
182        SerializedSavepoint::Ref(data)
183    }
184
185    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
186    where
187        Self: 'b,
188    {
189        value.data()
190    }
191
192    fn type_name() -> TypeName {
193        TypeName::internal("redb::SerializedSavepoint")
194    }
195}