Skip to main content

reddb_server/storage/wal/
mod.rs

1//! Write-Ahead Log (WAL) for RedDB durability.
2//!
3//! Handles crash recovery by logging all changes before they are applied to the database file.
4//!
5//! # Format
6//!
7//! - **Header:** `RDBW` (4 bytes) + Version (1 byte) + Reserved (3 bytes)
8//! - **Records:** Sequence of variable-length records.
9//! - **Checksum:** Each record ends with a CRC32 checksum.
10
11pub mod append_coordinator;
12pub mod archiver;
13pub mod checkpoint;
14pub mod checkpointer_task;
15pub mod group_commit;
16pub mod reader;
17pub mod record;
18pub mod recovery;
19pub mod rmgr;
20pub mod transaction;
21pub mod writer;
22
23pub use append_coordinator::WalAppendCoordinator;
24pub use archiver::{
25    archive_change_records, archive_snapshot, load_archived_change_records, load_backup_head,
26    load_snapshot_manifest, load_unified_manifest, load_wal_segment_manifest, publish_backup_head,
27    publish_snapshot_manifest, publish_unified_manifest, publish_unified_manifest_for_prefix,
28    publish_wal_segment_manifest, sha256_bytes_hex, sha256_file_hex, snapshot_manifest_key,
29    unified_manifest_key, wal_segment_manifest_key, BackupHead, SnapshotManifest, UnifiedManifest,
30    UnifiedSnapshotEntry, UnifiedWalEntry, WalArchiver, WalSegmentManifest, WalSegmentMeta,
31};
32pub use checkpoint::{CheckpointError, CheckpointMode, CheckpointResult, Checkpointer};
33pub use group_commit::GroupCommit;
34pub use reader::WalReader;
35pub use record::{RecordType, WalRecord};
36pub use recovery::{PointInTimeRecovery, RecoveryResult, RestorePoint};
37pub use transaction::{Transaction, TransactionManager, TxError, TxState};
38pub use writer::WalWriter;
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use std::fs;
44    use std::time::{SystemTime, UNIX_EPOCH};
45
46    #[test]
47    fn test_wal_write_read() {
48        let timestamp = SystemTime::now()
49            .duration_since(UNIX_EPOCH)
50            .unwrap()
51            .as_nanos();
52        let dir = std::env::temp_dir().join(format!("reddb_wal_test_{}", timestamp));
53        let _ = fs::create_dir_all(&dir);
54        let path = dir.join("test.wal");
55        if path.exists() {
56            fs::remove_file(&path).unwrap();
57        }
58
59        // Write
60        {
61            let mut writer = WalWriter::open(&path).unwrap();
62
63            let rec1 = WalRecord::Begin { tx_id: 100 };
64            writer.append(&rec1).unwrap();
65
66            let rec2 = WalRecord::PageWrite {
67                tx_id: 100,
68                page_id: 5,
69                data: vec![1, 2, 3, 4],
70            };
71            writer.append(&rec2).unwrap();
72
73            let rec3 = WalRecord::Commit { tx_id: 100 };
74            writer.append(&rec3).unwrap();
75        }
76
77        // Read
78        {
79            let reader = WalReader::open(&path).unwrap();
80            let records: Vec<_> = reader.iter().map(|r| r.unwrap().1).collect();
81
82            assert_eq!(records.len(), 3);
83
84            match &records[0] {
85                WalRecord::Begin { tx_id } => assert_eq!(*tx_id, 100),
86                _ => panic!("Wrong type"),
87            }
88
89            match &records[1] {
90                WalRecord::PageWrite {
91                    tx_id,
92                    page_id,
93                    data,
94                } => {
95                    assert_eq!(*tx_id, 100);
96                    assert_eq!(*page_id, 5);
97                    assert_eq!(*data, vec![1, 2, 3, 4]);
98                }
99                _ => panic!("Wrong type"),
100            }
101
102            match &records[2] {
103                WalRecord::Commit { tx_id } => assert_eq!(*tx_id, 100),
104                _ => panic!("Wrong type"),
105            }
106        }
107
108        // Cleanup
109        if path.exists() {
110            fs::remove_file(path).unwrap();
111        }
112        if dir.exists() {
113            fs::remove_dir(dir).unwrap();
114        }
115    }
116}