reddb_server/storage/wal/
reader.rs1use super::record::WalRecord;
2use std::fs::File;
3use std::io::{self, BufReader, Read};
4use std::path::Path;
5
6pub struct WalReader {
8 reader: BufReader<File>,
9 position: u64,
10 format_version: u8,
11}
12
13impl WalReader {
14 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
16 let file = File::open(path)?;
17 let mut reader = BufReader::new(file);
18
19 let mut header = [0u8; reddb_file::WAL_FILE_HEADER_BYTES];
21 reader.read_exact(&mut header)?;
22 let decoded = reddb_file::decode_wal_file_header(&header)?;
23
24 Ok(Self {
25 reader,
26 position: reddb_file::WAL_FILE_HEADER_BYTES as u64,
27 format_version: decoded.version,
28 })
29 }
30
31 pub fn iter(self) -> WalIterator {
34 WalIterator {
35 reader: self.reader,
36 position: self.position,
37 format_version: self.format_version,
38 }
39 }
40
41 pub fn collect_full_page_images<P: AsRef<Path>>(
46 path: P,
47 ) -> io::Result<std::collections::BTreeMap<u32, (u64, Vec<u8>)>> {
48 use std::collections::BTreeMap;
49 let mut out: BTreeMap<u32, (u64, Vec<u8>)> = BTreeMap::new();
50 if !path.as_ref().exists() {
51 return Ok(out);
52 }
53 let reader = Self::open(path)?;
54 for item in reader.iter() {
55 let (lsn, record) = item?;
56 if let WalRecord::FullPageImage { page_id, data, .. } = record {
57 match out.get(&page_id) {
58 Some((existing_lsn, _)) if *existing_lsn > lsn => {}
59 _ => {
60 out.insert(page_id, (lsn, data));
61 }
62 }
63 }
64 }
65 Ok(out)
66 }
67}
68
69pub struct WalIterator {
70 reader: BufReader<File>,
71 position: u64,
72 format_version: u8,
73}
74
75impl WalIterator {
76 pub(crate) fn next_record_lsn(&self) -> u64 {
77 self.position
78 }
79}
80
81impl Iterator for WalIterator {
82 type Item = io::Result<(u64, WalRecord)>;
83
84 fn next(&mut self) -> Option<Self::Item> {
85 let start_pos = self.position;
96
97 struct CountingReader<'a, R> {
100 inner: &'a mut R,
101 count: u64,
102 }
103
104 impl<'a, R: Read> Read for CountingReader<'a, R> {
105 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
106 let n = self.inner.read(buf)?;
107 self.count += n as u64;
108 Ok(n)
109 }
110 }
111
112 let mut counter = CountingReader {
113 inner: &mut self.reader,
114 count: 0,
115 };
116
117 match WalRecord::read_with_format_version(&mut counter, self.format_version) {
118 Ok(Some((_, record))) => {
119 self.position += counter.count;
120 Some(Ok((start_pos, record)))
121 }
122 Ok(None) => None, Err(e) => Some(Err(e)),
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::super::writer::WalWriter;
131 use super::*;
132 use std::path::PathBuf;
133
134 struct FileGuard {
135 path: PathBuf,
136 }
137
138 impl Drop for FileGuard {
139 fn drop(&mut self) {
140 let _ = std::fs::remove_file(&self.path);
141 }
142 }
143
144 fn temp_wal(name: &str) -> (FileGuard, PathBuf) {
145 let path = reddb_file::layout::wal_component_temp_path(
146 &std::env::temp_dir(),
147 "reader",
148 name,
149 std::process::id(),
150 );
151 let guard = FileGuard { path: path.clone() };
152 let _ = std::fs::remove_file(&path);
153 (guard, path)
154 }
155
156 #[test]
157 fn test_read_empty_wal() {
158 let (_guard, path) = temp_wal("empty");
159
160 {
162 let _writer = WalWriter::open(&path).unwrap();
163 }
164
165 let reader = WalReader::open(&path).unwrap();
167 let records: Vec<_> = reader.iter().collect();
168 assert!(records.is_empty());
169 }
170
171 #[test]
172 fn test_read_single_record() {
173 let (_guard, path) = temp_wal("single");
174
175 {
177 let mut writer = WalWriter::open(&path).unwrap();
178 writer.append(&WalRecord::Begin { tx_id: 42 }).unwrap();
179 }
180
181 let reader = WalReader::open(&path).unwrap();
183 let records: Vec<_> = reader.iter().collect();
184
185 assert_eq!(records.len(), 1);
186 let (lsn, record) = records[0].as_ref().unwrap();
187 assert_eq!(*lsn, 8);
188 assert_eq!(*record, WalRecord::Begin { tx_id: 42 });
189 }
190
191 #[test]
192 fn test_read_multiple_records() {
193 let (_guard, path) = temp_wal("multi");
194
195 {
197 let mut writer = WalWriter::open(&path).unwrap();
198 writer.append(&WalRecord::Begin { tx_id: 1 }).unwrap();
199 writer
200 .append(&WalRecord::PageWrite {
201 tx_id: 1,
202 page_id: 10,
203 data: vec![1, 2, 3],
204 })
205 .unwrap();
206 writer.append(&WalRecord::Commit { tx_id: 1 }).unwrap();
207 }
208
209 let reader = WalReader::open(&path).unwrap();
211 let records: Vec<_> = reader.iter().collect();
212
213 assert_eq!(records.len(), 3);
214
215 match &records[0].as_ref().unwrap().1 {
217 WalRecord::Begin { tx_id } => assert_eq!(*tx_id, 1),
218 _ => panic!("Expected Begin"),
219 }
220 match &records[1].as_ref().unwrap().1 {
221 WalRecord::PageWrite {
222 tx_id,
223 page_id,
224 data,
225 } => {
226 assert_eq!(*tx_id, 1);
227 assert_eq!(*page_id, 10);
228 assert_eq!(data, &vec![1, 2, 3]);
229 }
230 _ => panic!("Expected PageWrite"),
231 }
232 match &records[2].as_ref().unwrap().1 {
233 WalRecord::Commit { tx_id } => assert_eq!(*tx_id, 1),
234 _ => panic!("Expected Commit"),
235 }
236 }
237
238 #[test]
239 fn test_lsn_tracking() {
240 let (_guard, path) = temp_wal("lsn");
241
242 let (lsn1, lsn2, lsn3);
244 {
245 let mut writer = WalWriter::open(&path).unwrap();
246 lsn1 = writer.append(&WalRecord::Begin { tx_id: 1 }).unwrap();
247 lsn2 = writer.append(&WalRecord::Checkpoint { lsn: 100 }).unwrap();
248 lsn3 = writer.append(&WalRecord::Rollback { tx_id: 1 }).unwrap();
249 }
250
251 let reader = WalReader::open(&path).unwrap();
253 let records: Vec<_> = reader.iter().collect();
254
255 assert_eq!(records.len(), 3);
256 assert_eq!(records[0].as_ref().unwrap().0, lsn1);
257 assert_eq!(records[1].as_ref().unwrap().0, lsn2);
258 assert_eq!(records[2].as_ref().unwrap().0, lsn3);
259 }
260
261 #[test]
262 fn test_invalid_magic() {
263 let (_guard, path) = temp_wal("badmagic");
264
265 std::fs::write(&path, b"BAAD0000").unwrap();
267
268 let result = WalReader::open(&path);
269 assert!(result.is_err());
270 }
271
272 #[test]
273 fn test_invalid_version() {
274 let (_guard, path) = temp_wal("badver");
275
276 let mut header = reddb_file::encode_wal_file_header();
278 header[4] = 99; std::fs::write(&path, header).unwrap();
280
281 let result = WalReader::open(&path);
282 assert!(result.is_err());
283 }
284
285 #[test]
286 fn test_read_large_page_write() {
287 let (_guard, path) = temp_wal("large");
288
289 let large_data: Vec<u8> = (0..4096).map(|i| (i % 256) as u8).collect();
290
291 {
293 let mut writer = WalWriter::open(&path).unwrap();
294 writer
295 .append(&WalRecord::PageWrite {
296 tx_id: 1,
297 page_id: 0,
298 data: large_data.clone(),
299 })
300 .unwrap();
301 }
302
303 let reader = WalReader::open(&path).unwrap();
305 let records: Vec<_> = reader.iter().collect();
306
307 assert_eq!(records.len(), 1);
308 match &records[0].as_ref().unwrap().1 {
309 WalRecord::PageWrite { data, .. } => {
310 assert_eq!(*data, large_data);
311 }
312 _ => panic!("Expected PageWrite"),
313 }
314 }
315}