1use rustlite_core::Result;
5use serde::{Deserialize, Serialize};
6
7pub mod reader;
8pub mod record;
9pub mod recovery;
10pub mod segment;
11pub mod writer;
12
13pub use reader::WalReader;
14pub use record::{RecordPayload, RecordType, WalRecord};
15pub use recovery::{RecoveryManager, RecoveryStats};
16pub use segment::{SegmentInfo, SegmentManager};
17pub use writer::WalWriter;
18
19#[derive(Debug, Clone)]
21pub struct WalConfig {
22 pub sync_mode: SyncMode,
24 pub max_segment_size: u64,
26 pub wal_dir: std::path::PathBuf,
28}
29
30impl Default for WalConfig {
31 fn default() -> Self {
32 Self {
33 sync_mode: SyncMode::Sync,
34 max_segment_size: 64 * 1024 * 1024, wal_dir: std::path::PathBuf::from("wal"),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum SyncMode {
43 Sync,
45 Async,
47 None,
49}
50
51pub struct WalManager {
53 config: WalConfig,
54 writer: Option<WalWriter>,
55}
56
57impl WalManager {
58 pub fn new(config: WalConfig) -> Result<Self> {
59 Ok(Self {
60 config,
61 writer: None,
62 })
63 }
64
65 pub fn open(&mut self) -> Result<()> {
69 let writer = WalWriter::new(
70 &self.config.wal_dir,
71 self.config.max_segment_size,
72 self.config.sync_mode,
73 )?;
74 self.writer = Some(writer);
75
76 Ok(())
77 }
78
79 pub fn append(&mut self, record: WalRecord) -> Result<u64> {
81 let writer = self
82 .writer
83 .as_mut()
84 .ok_or_else(|| rustlite_core::Error::InvalidOperation("WAL not opened".to_string()))?;
85 writer.append(record)
86 }
87
88 pub fn sync(&mut self) -> Result<()> {
90 if let Some(writer) = &mut self.writer {
91 writer.sync()
92 } else {
93 Ok(())
94 }
95 }
96
97 pub fn close(&mut self) -> Result<()> {
99 if let Some(mut writer) = self.writer.take() {
100 writer.sync()?;
101 }
102 Ok(())
103 }
104
105 pub fn recover(&self) -> Result<Vec<WalRecord>> {
110 let recovery = RecoveryManager::new(self.config.clone())?;
111 recovery.recover()
112 }
113
114 pub fn recover_with_markers(&self) -> Result<Vec<WalRecord>> {
118 let recovery = RecoveryManager::new(self.config.clone())?;
119 recovery.recover_with_markers()
120 }
121
122 pub fn stats(&self) -> Result<RecoveryStats> {
124 let recovery = RecoveryManager::new(self.config.clone())?;
125 recovery.get_stats()
126 }
127
128 pub fn reader(&self) -> Result<WalReader> {
130 WalReader::new(&self.config.wal_dir)
131 }
132
133 pub fn segment_manager(&self) -> SegmentManager {
135 SegmentManager::new(self.config.wal_dir.clone())
136 }
137
138 pub fn config(&self) -> &WalConfig {
140 &self.config
141 }
142
143 pub fn is_open(&self) -> bool {
145 self.writer.is_some()
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use tempfile::TempDir;
153
154 fn setup_test_config() -> (TempDir, WalConfig) {
155 let temp_dir = TempDir::new().expect("Failed to create temp dir");
156 let wal_path = temp_dir.path().join("wal");
157 std::fs::create_dir_all(&wal_path).expect("Failed to create WAL dir");
158
159 let config = WalConfig {
160 wal_dir: wal_path,
161 sync_mode: SyncMode::Sync,
162 max_segment_size: 64 * 1024 * 1024,
163 };
164
165 (temp_dir, config)
166 }
167
168 #[test]
169 fn test_wal_config_default() {
170 let config = WalConfig::default();
171 assert_eq!(config.sync_mode, SyncMode::Sync);
172 assert_eq!(config.max_segment_size, 64 * 1024 * 1024);
173 }
174
175 #[test]
176 fn test_sync_mode() {
177 assert_eq!(SyncMode::Sync, SyncMode::Sync);
178 assert_ne!(SyncMode::Sync, SyncMode::Async);
179 }
180
181 #[test]
182 fn test_wal_manager_lifecycle() {
183 let (_temp_dir, config) = setup_test_config();
184
185 let mut manager = WalManager::new(config).expect("Failed to create manager");
186 assert!(!manager.is_open());
187
188 manager.open().expect("Failed to open");
189 assert!(manager.is_open());
190
191 manager.close().expect("Failed to close");
192 assert!(!manager.is_open());
193 }
194
195 #[test]
196 fn test_wal_manager_write_and_recover() {
197 let (_temp_dir, config) = setup_test_config();
198
199 {
201 let mut manager = WalManager::new(config.clone()).expect("Failed to create manager");
202 manager.open().expect("Failed to open");
203
204 for i in 0..5 {
205 let record = WalRecord::put(
206 format!("key{}", i).into_bytes(),
207 format!("value{}", i).into_bytes(),
208 );
209 manager.append(record).expect("Failed to append");
210 }
211
212 manager.sync().expect("Failed to sync");
213 manager.close().expect("Failed to close");
214 }
215
216 {
218 let manager = WalManager::new(config).expect("Failed to create manager");
219 let records = manager.recover().expect("Failed to recover");
220
221 assert_eq!(records.len(), 5);
222 }
223 }
224
225 #[test]
226 fn test_wal_manager_stats() {
227 let (_temp_dir, config) = setup_test_config();
228
229 {
231 let mut manager = WalManager::new(config.clone()).expect("Failed to create manager");
232 manager.open().expect("Failed to open");
233
234 manager.append(WalRecord::begin_tx(1)).expect("Failed");
235 manager.append(WalRecord::put(b"k".to_vec(), b"v".to_vec())).expect("Failed");
236 manager.append(WalRecord::commit_tx(1)).expect("Failed");
237
238 manager.close().expect("Failed to close");
239 }
240
241 let manager = WalManager::new(config).expect("Failed to create manager");
242 let stats = manager.stats().expect("Failed to get stats");
243
244 assert_eq!(stats.total_records, 3);
245 assert_eq!(stats.transactions_started, 1);
246 assert_eq!(stats.transactions_committed, 1);
247 }
248
249 #[test]
250 fn test_wal_manager_segment_manager() {
251 let (_temp_dir, config) = setup_test_config();
252
253 {
254 let mut manager = WalManager::new(config.clone()).expect("Failed to create manager");
255 manager.open().expect("Failed to open");
256 manager.append(WalRecord::put(b"k".to_vec(), b"v".to_vec())).expect("Failed");
257 manager.close().expect("Failed to close");
258 }
259
260 let manager = WalManager::new(config).expect("Failed to create manager");
261 let seg_manager = manager.segment_manager();
262
263 assert_eq!(seg_manager.segment_count().unwrap(), 1);
264 }
265}