1use std::collections::HashMap;
17use thiserror::Error;
18
19#[inline]
25pub fn fnv1a_64(data: &[u8]) -> u64 {
26 let mut h: u64 = 14_695_981_039_346_656_037;
27 for &b in data {
28 h ^= b as u64;
29 h = h.wrapping_mul(1_099_511_628_211);
30 }
31 h
32}
33
34pub const WAL_MAGIC: [u8; 4] = *b"WALX";
36
37const MIN_ENTRY_BYTES: usize = 45;
41
42#[derive(Debug, Error, PartialEq, Eq, Clone)]
48pub enum WalError {
49 #[error("checksum mismatch for entry {entry_seq}: expected {expected:#018x}, got {got:#018x}")]
51 ChecksumMismatch {
52 entry_seq: u64,
53 expected: u64,
54 got: u64,
55 },
56
57 #[error("corrupted entry at seq {0}")]
59 CorruptedEntry(u64),
60
61 #[error("transaction {0} not found")]
63 TransactionNotFound(u64),
64
65 #[error("entry too large: {0} bytes")]
67 EntryTooLarge(usize),
68
69 #[error("segment full")]
71 SegmentFull,
72
73 #[error("invalid WAL magic bytes")]
75 InvalidMagic,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84#[repr(u8)]
85pub enum WalOpType {
86 Put = 1,
88 Delete = 2,
90 Begin = 3,
92 Commit = 4,
94 Rollback = 5,
96 Checkpoint = 6,
98}
99
100impl WalOpType {
101 fn from_u8(v: u8) -> Option<Self> {
102 match v {
103 1 => Some(Self::Put),
104 2 => Some(Self::Delete),
105 3 => Some(Self::Begin),
106 4 => Some(Self::Commit),
107 5 => Some(Self::Rollback),
108 6 => Some(Self::Checkpoint),
109 _ => None,
110 }
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct WalEntry {
121 pub seq_num: u64,
123 pub tx_id: u64,
125 pub op_type: WalOpType,
127 pub key: Vec<u8>,
129 pub value: Vec<u8>,
131 pub timestamp: u64,
133 pub checksum: u64,
135}
136
137impl WalEntry {
138 pub fn encode(&self) -> Vec<u8> {
144 let total = 4 + 8 + 8 + 1 + 4 + self.key.len() + 4 + self.value.len() + 8 + 8;
145 let mut buf = Vec::with_capacity(total);
146
147 buf.extend_from_slice(&WAL_MAGIC);
148 buf.extend_from_slice(&self.seq_num.to_le_bytes());
149 buf.extend_from_slice(&self.tx_id.to_le_bytes());
150 buf.push(self.op_type as u8);
151 buf.extend_from_slice(&(self.key.len() as u32).to_le_bytes());
152 buf.extend_from_slice(&self.key);
153 buf.extend_from_slice(&(self.value.len() as u32).to_le_bytes());
154 buf.extend_from_slice(&self.value);
155 buf.extend_from_slice(&self.timestamp.to_le_bytes());
156
157 let checksum = fnv1a_64(&buf);
159 buf.extend_from_slice(&checksum.to_le_bytes());
160
161 buf
162 }
163
164 pub fn decode(data: &[u8], offset: usize) -> Result<(WalEntry, usize), WalError> {
174 let buf = &data[offset..];
175
176 if buf.len() < MIN_ENTRY_BYTES {
177 return Err(WalError::CorruptedEntry(0));
178 }
179
180 if buf[..4] != WAL_MAGIC {
182 return Err(WalError::InvalidMagic);
183 }
184
185 let seq_num = u64::from_le_bytes(
186 buf[4..12]
187 .try_into()
188 .map_err(|_| WalError::CorruptedEntry(0))?,
189 );
190 let tx_id = u64::from_le_bytes(
191 buf[12..20]
192 .try_into()
193 .map_err(|_| WalError::CorruptedEntry(seq_num))?,
194 );
195 let op_u8 = buf[20];
196 let op_type = WalOpType::from_u8(op_u8).ok_or(WalError::CorruptedEntry(seq_num))?;
197
198 let key_len = u32::from_le_bytes(
199 buf[21..25]
200 .try_into()
201 .map_err(|_| WalError::CorruptedEntry(seq_num))?,
202 ) as usize;
203
204 let key_end = 25 + key_len;
205 if buf.len() < key_end + 4 {
206 return Err(WalError::CorruptedEntry(seq_num));
207 }
208 let key = buf[25..key_end].to_vec();
209
210 let value_len = u32::from_le_bytes(
211 buf[key_end..key_end + 4]
212 .try_into()
213 .map_err(|_| WalError::CorruptedEntry(seq_num))?,
214 ) as usize;
215
216 let value_start = key_end + 4;
217 let value_end = value_start + value_len;
218 let ts_end = value_end + 8;
219 let cs_end = ts_end + 8;
220
221 if buf.len() < cs_end {
222 return Err(WalError::CorruptedEntry(seq_num));
223 }
224
225 let value = buf[value_start..value_end].to_vec();
226 let timestamp = u64::from_le_bytes(
227 buf[value_end..ts_end]
228 .try_into()
229 .map_err(|_| WalError::CorruptedEntry(seq_num))?,
230 );
231 let stored_checksum = u64::from_le_bytes(
232 buf[ts_end..cs_end]
233 .try_into()
234 .map_err(|_| WalError::CorruptedEntry(seq_num))?,
235 );
236
237 let computed = fnv1a_64(&buf[..ts_end]);
239 if computed != stored_checksum {
240 return Err(WalError::ChecksumMismatch {
241 entry_seq: seq_num,
242 expected: stored_checksum,
243 got: computed,
244 });
245 }
246
247 let entry = WalEntry {
248 seq_num,
249 tx_id,
250 op_type,
251 key,
252 value,
253 timestamp,
254 checksum: stored_checksum,
255 };
256
257 Ok((entry, cs_end))
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum TxState {
268 Active,
270 Committed,
272 RolledBack,
274}
275
276#[derive(Debug, Clone)]
278pub struct Transaction {
279 pub tx_id: u64,
281 pub entries: Vec<WalEntry>,
283 pub state: TxState,
285}
286
287impl Transaction {
288 fn new(tx_id: u64) -> Self {
289 Self {
290 tx_id,
291 entries: Vec::new(),
292 state: TxState::Active,
293 }
294 }
295}
296
297#[derive(Debug, Clone)]
303pub struct WalConfig {
304 pub max_segment_size: usize,
307
308 pub sync_on_write: bool,
311
312 pub max_entries_per_segment: usize,
315
316 pub checkpoint_interval: usize,
319}
320
321impl Default for WalConfig {
322 fn default() -> Self {
323 Self {
324 max_segment_size: 64 * 1024 * 1024, sync_on_write: false,
326 max_entries_per_segment: 100_000,
327 checkpoint_interval: 10_000,
328 }
329 }
330}
331
332#[derive(Debug, Clone, Default, PartialEq, Eq)]
338pub struct RecoveryResult {
339 pub entries_recovered: usize,
341 pub transactions_recovered: usize,
343 pub entries_replayed: usize,
345 pub partial_tx_discarded: usize,
347 pub last_seq_num: u64,
349}
350
351#[derive(Debug, Clone, Default, PartialEq, Eq)]
357pub struct WalStats {
358 pub total_entries: u64,
360 pub total_bytes: u64,
362 pub active_transactions: usize,
364 pub segments_count: usize,
366 pub last_checkpoint_seq: u64,
368}
369
370pub struct WalWriteAheadLog {
393 segment: Vec<u8>,
395 entries: Vec<WalEntry>,
397 transactions: HashMap<u64, Transaction>,
399 next_seq: u64,
401 next_tx_id: u64,
403 config: WalConfig,
405 stats: WalStats,
407 last_checkpoint_seq: u64,
409 writes_since_checkpoint: usize,
411}
412
413impl WalWriteAheadLog {
414 pub fn new(config: WalConfig) -> Self {
416 Self {
417 segment: Vec::new(),
418 entries: Vec::new(),
419 transactions: HashMap::new(),
420 next_seq: 1,
421 next_tx_id: 1,
422 config,
423 stats: WalStats {
424 segments_count: 1,
425 ..WalStats::default()
426 },
427 last_checkpoint_seq: 0,
428 writes_since_checkpoint: 0,
429 }
430 }
431
432 fn now_ns() -> u64 {
434 use std::time::{SystemTime, UNIX_EPOCH};
435 SystemTime::now()
436 .duration_since(UNIX_EPOCH)
437 .unwrap_or_default()
438 .as_nanos() as u64
439 }
440
441 fn alloc_seq(&mut self) -> u64 {
443 let s = self.next_seq;
444 self.next_seq += 1;
445 s
446 }
447
448 fn alloc_tx_id(&mut self) -> u64 {
450 let id = self.next_tx_id;
451 self.next_tx_id += 1;
452 id
453 }
454
455 fn append_entry(&mut self, entry: WalEntry) -> Result<u64, WalError> {
457 let encoded = entry.encode();
458 let encoded_len = encoded.len();
459
460 if self.config.max_segment_size > 0
462 && self.segment.len() + encoded_len > self.config.max_segment_size
463 {
464 return Err(WalError::SegmentFull);
465 }
466
467 if self.config.max_entries_per_segment > 0
469 && self.entries.len() >= self.config.max_entries_per_segment
470 {
471 return Err(WalError::SegmentFull);
472 }
473
474 let seq = entry.seq_num;
475 self.segment.extend_from_slice(&encoded);
476 self.stats.total_entries += 1;
477 self.stats.total_bytes += encoded_len as u64;
478 self.entries.push(entry);
479
480 Ok(seq)
481 }
482
483 pub fn write(&mut self, key: Vec<u8>, value: Vec<u8>, op: WalOpType) -> Result<u64, WalError> {
490 let seq = self.alloc_seq();
491 let entry = WalEntry {
492 seq_num: seq,
493 tx_id: 0,
494 op_type: op,
495 key,
496 value,
497 timestamp: Self::now_ns(),
498 checksum: 0, };
500 let seq = self.append_entry(entry)?;
501
502 self.writes_since_checkpoint += 1;
503 if self.config.checkpoint_interval > 0
504 && self.writes_since_checkpoint >= self.config.checkpoint_interval
505 {
506 self.checkpoint()?;
507 }
508
509 Ok(seq)
510 }
511
512 pub fn begin_transaction(&mut self) -> u64 {
514 let tx_id = self.alloc_tx_id();
515 let seq = self.alloc_seq();
516 let entry = WalEntry {
517 seq_num: seq,
518 tx_id,
519 op_type: WalOpType::Begin,
520 key: Vec::new(),
521 value: Vec::new(),
522 timestamp: Self::now_ns(),
523 checksum: 0,
524 };
525 let _ = self.append_entry(entry);
528
529 let tx = Transaction::new(tx_id);
530 self.transactions.insert(tx_id, tx);
531 self.stats.active_transactions = self
532 .transactions
533 .values()
534 .filter(|t| t.state == TxState::Active)
535 .count();
536
537 tx_id
538 }
539
540 pub fn write_tx(
542 &mut self,
543 tx_id: u64,
544 key: Vec<u8>,
545 value: Vec<u8>,
546 op: WalOpType,
547 ) -> Result<u64, WalError> {
548 {
550 let tx = self
551 .transactions
552 .get(&tx_id)
553 .ok_or(WalError::TransactionNotFound(tx_id))?;
554 if tx.state != TxState::Active {
555 return Err(WalError::TransactionNotFound(tx_id));
556 }
557 }
558
559 let seq = self.alloc_seq();
560 let entry = WalEntry {
561 seq_num: seq,
562 tx_id,
563 op_type: op,
564 key,
565 value,
566 timestamp: Self::now_ns(),
567 checksum: 0,
568 };
569
570 let seq = self.append_entry(entry.clone())?;
571
572 if let Some(tx) = self.transactions.get_mut(&tx_id) {
574 tx.entries.push(entry);
575 }
576
577 Ok(seq)
578 }
579
580 pub fn commit_transaction(&mut self, tx_id: u64) -> Result<(), WalError> {
582 {
583 let tx = self
584 .transactions
585 .get(&tx_id)
586 .ok_or(WalError::TransactionNotFound(tx_id))?;
587 if tx.state != TxState::Active {
588 return Err(WalError::TransactionNotFound(tx_id));
589 }
590 }
591
592 let seq = self.alloc_seq();
593 let entry = WalEntry {
594 seq_num: seq,
595 tx_id,
596 op_type: WalOpType::Commit,
597 key: Vec::new(),
598 value: Vec::new(),
599 timestamp: Self::now_ns(),
600 checksum: 0,
601 };
602 self.append_entry(entry)?;
603
604 if let Some(tx) = self.transactions.get_mut(&tx_id) {
605 tx.state = TxState::Committed;
606 }
607
608 self.stats.active_transactions = self
609 .transactions
610 .values()
611 .filter(|t| t.state == TxState::Active)
612 .count();
613
614 Ok(())
615 }
616
617 pub fn rollback_transaction(&mut self, tx_id: u64) -> Result<(), WalError> {
619 {
620 let tx = self
621 .transactions
622 .get(&tx_id)
623 .ok_or(WalError::TransactionNotFound(tx_id))?;
624 if tx.state != TxState::Active {
625 return Err(WalError::TransactionNotFound(tx_id));
626 }
627 }
628
629 let seq = self.alloc_seq();
630 let entry = WalEntry {
631 seq_num: seq,
632 tx_id,
633 op_type: WalOpType::Rollback,
634 key: Vec::new(),
635 value: Vec::new(),
636 timestamp: Self::now_ns(),
637 checksum: 0,
638 };
639 self.append_entry(entry)?;
640
641 if let Some(tx) = self.transactions.get_mut(&tx_id) {
642 tx.state = TxState::RolledBack;
643 }
644
645 self.stats.active_transactions = self
646 .transactions
647 .values()
648 .filter(|t| t.state == TxState::Active)
649 .count();
650
651 Ok(())
652 }
653
654 pub fn checkpoint(&mut self) -> Result<u64, WalError> {
661 let seq = self.alloc_seq();
662 let entry = WalEntry {
663 seq_num: seq,
664 tx_id: 0,
665 op_type: WalOpType::Checkpoint,
666 key: Vec::new(),
667 value: Vec::new(),
668 timestamp: Self::now_ns(),
669 checksum: 0,
670 };
671 self.append_entry(entry)?;
672
673 let active_tx_ids: std::collections::HashSet<u64> = self
676 .transactions
677 .values()
678 .filter(|t| t.state == TxState::Active)
679 .map(|t| t.tx_id)
680 .collect();
681
682 let truncate_before = self.last_checkpoint_seq;
683
684 self.entries
688 .retain(|e| e.seq_num > truncate_before || active_tx_ids.contains(&e.tx_id));
689
690 let mut rebuilt = Vec::with_capacity(self.segment.len());
692 for e in &self.entries {
693 rebuilt.extend_from_slice(&e.encode());
694 }
695 self.segment = rebuilt;
696
697 self.last_checkpoint_seq = seq;
698 self.stats.last_checkpoint_seq = seq;
699 self.writes_since_checkpoint = 0;
700
701 Ok(seq)
702 }
703
704 pub fn recover(data: &[u8]) -> Result<RecoveryResult, WalError> {
710 let mut all_entries: Vec<WalEntry> = Vec::new();
711 let mut pos = 0usize;
712
713 while pos < data.len() {
714 if pos + 4 <= data.len() && data[pos..pos + 4] != WAL_MAGIC {
717 pos += 1;
718 continue;
719 }
720
721 match WalEntry::decode(data, pos) {
722 Ok((entry, consumed)) => {
723 pos += consumed;
724 all_entries.push(entry);
725 }
726 Err(WalError::ChecksumMismatch { .. }) => {
727 break;
729 }
730 Err(_) => {
731 break;
733 }
734 }
735 }
736
737 let mut committed_tx: std::collections::HashSet<u64> = std::collections::HashSet::new();
739 let mut rolled_back_tx: std::collections::HashSet<u64> = std::collections::HashSet::new();
740 let mut seen_tx: std::collections::HashSet<u64> = std::collections::HashSet::new();
741
742 for e in &all_entries {
743 if e.tx_id != 0 {
744 seen_tx.insert(e.tx_id);
745 match e.op_type {
746 WalOpType::Commit => {
747 committed_tx.insert(e.tx_id);
748 }
749 WalOpType::Rollback => {
750 rolled_back_tx.insert(e.tx_id);
751 }
752 _ => {}
753 }
754 }
755 }
756
757 let last_seq_num = all_entries.iter().map(|e| e.seq_num).max().unwrap_or(0);
758
759 let entries_recovered = all_entries.len();
760 let transactions_recovered = seen_tx.len();
761
762 let partial_tx_ids: std::collections::HashSet<u64> = seen_tx
765 .iter()
766 .filter(|id| !committed_tx.contains(id) && !rolled_back_tx.contains(id))
767 .copied()
768 .collect();
769
770 let partial_tx_discarded: usize = all_entries
771 .iter()
772 .filter(|e| e.tx_id != 0 && partial_tx_ids.contains(&e.tx_id))
773 .count();
774
775 let entries_replayed: usize = all_entries
778 .iter()
779 .filter(|e| {
780 let is_data_op = matches!(e.op_type, WalOpType::Put | WalOpType::Delete);
781 if !is_data_op {
782 return false;
783 }
784 if e.tx_id == 0 {
785 return true;
787 }
788 committed_tx.contains(&e.tx_id)
790 })
791 .count();
792
793 Ok(RecoveryResult {
794 entries_recovered,
795 transactions_recovered,
796 entries_replayed,
797 partial_tx_discarded,
798 last_seq_num,
799 })
800 }
801
802 pub fn replay(entries: &[WalEntry], handler: &mut dyn FnMut(&WalEntry)) -> usize {
809 let committed_tx: std::collections::HashSet<u64> = entries
811 .iter()
812 .filter(|e| e.op_type == WalOpType::Commit && e.tx_id != 0)
813 .map(|e| e.tx_id)
814 .collect();
815
816 let mut count = 0usize;
818 for entry in entries {
819 let is_data = matches!(entry.op_type, WalOpType::Put | WalOpType::Delete);
820 if !is_data {
821 continue;
822 }
823 let replayable = entry.tx_id == 0 || committed_tx.contains(&entry.tx_id);
824 if replayable {
825 handler(entry);
826 count += 1;
827 }
828 }
829 count
830 }
831
832 pub fn export_segment(&self) -> Vec<u8> {
834 self.segment.clone()
835 }
836
837 pub fn stats(&self) -> WalStats {
839 WalStats {
840 total_entries: self.stats.total_entries,
841 total_bytes: self.stats.total_bytes,
842 active_transactions: self.stats.active_transactions,
843 segments_count: 1,
844 last_checkpoint_seq: self.last_checkpoint_seq,
845 }
846 }
847
848 pub fn truncate_before(&mut self, seq_num: u64) -> usize {
851 let before = self.entries.len();
852 self.entries.retain(|e| e.seq_num >= seq_num);
853 let removed = before - self.entries.len();
854
855 if removed > 0 {
856 let mut rebuilt = Vec::with_capacity(self.segment.len());
857 for e in &self.entries {
858 rebuilt.extend_from_slice(&e.encode());
859 }
860 self.segment = rebuilt;
861 }
862
863 removed
864 }
865
866 pub fn entry_count(&self) -> usize {
868 self.entries.len()
869 }
870}
871
872#[cfg(test)]
877mod tests {
878 use super::*;
879
880 struct Xorshift64 {
883 state: u64,
884 }
885
886 impl Xorshift64 {
887 fn new(seed: u64) -> Self {
888 Self {
889 state: if seed == 0 {
890 0xDEAD_BEEF_CAFE_1234
891 } else {
892 seed
893 },
894 }
895 }
896
897 fn next(&mut self) -> u64 {
898 self.state ^= self.state << 13;
899 self.state ^= self.state >> 7;
900 self.state ^= self.state << 17;
901 self.state
902 }
903
904 fn next_bytes(&mut self, len: usize) -> Vec<u8> {
905 let mut out = Vec::with_capacity(len);
906 while out.len() < len {
907 let v = self.next();
908 out.extend_from_slice(&v.to_le_bytes());
909 }
910 out.truncate(len);
911 out
912 }
913 }
914
915 fn default_wal() -> WalWriteAheadLog {
916 WalWriteAheadLog::new(WalConfig::default())
917 }
918
919 #[test]
922 fn test_fnv1a_64_empty() {
923 assert_eq!(fnv1a_64(&[]), 14_695_981_039_346_656_037u64);
924 }
925
926 #[test]
927 fn test_fnv1a_64_known_value() {
928 let h = fnv1a_64(b"foobar");
930 assert_ne!(h, 0);
931 assert_eq!(h, fnv1a_64(b"foobar"));
933 }
934
935 #[test]
936 fn test_fnv1a_64_differs_by_one_byte() {
937 let a = fnv1a_64(b"hello");
938 let b = fnv1a_64(b"hEllo");
939 assert_ne!(a, b);
940 }
941
942 #[test]
945 fn test_wal_op_type_from_u8_all_valid() {
946 for (byte, expected) in &[
947 (1u8, WalOpType::Put),
948 (2, WalOpType::Delete),
949 (3, WalOpType::Begin),
950 (4, WalOpType::Commit),
951 (5, WalOpType::Rollback),
952 (6, WalOpType::Checkpoint),
953 ] {
954 assert_eq!(WalOpType::from_u8(*byte), Some(*expected));
955 }
956 }
957
958 #[test]
959 fn test_wal_op_type_from_u8_invalid() {
960 assert_eq!(WalOpType::from_u8(0), None);
961 assert_eq!(WalOpType::from_u8(7), None);
962 assert_eq!(WalOpType::from_u8(255), None);
963 }
964
965 #[test]
968 fn test_encode_decode_put_entry() {
969 let key = b"my-key".to_vec();
970 let value = b"my-value-data".to_vec();
971 let entry = WalEntry {
972 seq_num: 42,
973 tx_id: 0,
974 op_type: WalOpType::Put,
975 key: key.clone(),
976 value: value.clone(),
977 timestamp: 123_456_789,
978 checksum: 0,
979 };
980 let encoded = entry.encode();
981 let cs = u64::from_le_bytes(encoded[encoded.len() - 8..].try_into().unwrap());
983
984 let (decoded, consumed) = WalEntry::decode(&encoded, 0).unwrap();
985 assert_eq!(consumed, encoded.len());
986 assert_eq!(decoded.seq_num, 42);
987 assert_eq!(decoded.tx_id, 0);
988 assert_eq!(decoded.op_type, WalOpType::Put);
989 assert_eq!(decoded.key, key);
990 assert_eq!(decoded.value, value);
991 assert_eq!(decoded.timestamp, 123_456_789);
992 assert_eq!(decoded.checksum, cs);
993 }
994
995 #[test]
996 fn test_encode_decode_delete_entry() {
997 let entry = WalEntry {
998 seq_num: 1,
999 tx_id: 5,
1000 op_type: WalOpType::Delete,
1001 key: b"del-key".to_vec(),
1002 value: Vec::new(),
1003 timestamp: 0,
1004 checksum: 0,
1005 };
1006 let enc = entry.encode();
1007 let (dec, _) = WalEntry::decode(&enc, 0).unwrap();
1008 assert_eq!(dec.op_type, WalOpType::Delete);
1009 assert_eq!(dec.key, b"del-key");
1010 assert!(dec.value.is_empty());
1011 }
1012
1013 #[test]
1014 fn test_encode_decode_empty_key_value() {
1015 let entry = WalEntry {
1016 seq_num: 99,
1017 tx_id: 0,
1018 op_type: WalOpType::Checkpoint,
1019 key: Vec::new(),
1020 value: Vec::new(),
1021 timestamp: 0,
1022 checksum: 0,
1023 };
1024 let enc = entry.encode();
1025 let (dec, consumed) = WalEntry::decode(&enc, 0).unwrap();
1026 assert_eq!(consumed, enc.len());
1027 assert_eq!(dec.op_type, WalOpType::Checkpoint);
1028 assert!(dec.key.is_empty());
1029 assert!(dec.value.is_empty());
1030 }
1031
1032 #[test]
1033 fn test_encode_decode_with_offset() {
1034 let padding = vec![0u8; 16];
1035 let entry = WalEntry {
1036 seq_num: 7,
1037 tx_id: 3,
1038 op_type: WalOpType::Put,
1039 key: b"k".to_vec(),
1040 value: b"v".to_vec(),
1041 timestamp: 1,
1042 checksum: 0,
1043 };
1044 let mut buf = padding.clone();
1045 buf.extend_from_slice(&entry.encode());
1046
1047 let (dec, _) = WalEntry::decode(&buf, 16).unwrap();
1048 assert_eq!(dec.seq_num, 7);
1049 }
1050
1051 #[test]
1052 fn test_encode_decode_large_payload() {
1053 let mut rng = Xorshift64::new(0xABCDEF01);
1054 let key = rng.next_bytes(512);
1055 let value = rng.next_bytes(4096);
1056 let entry = WalEntry {
1057 seq_num: 1000,
1058 tx_id: 0,
1059 op_type: WalOpType::Put,
1060 key: key.clone(),
1061 value: value.clone(),
1062 timestamp: 999,
1063 checksum: 0,
1064 };
1065 let enc = entry.encode();
1066 let (dec, _) = WalEntry::decode(&enc, 0).unwrap();
1067 assert_eq!(dec.key, key);
1068 assert_eq!(dec.value, value);
1069 }
1070
1071 #[test]
1074 fn test_checksum_mismatch_detected() {
1075 let entry = WalEntry {
1076 seq_num: 1,
1077 tx_id: 0,
1078 op_type: WalOpType::Put,
1079 key: b"k".to_vec(),
1080 value: b"v".to_vec(),
1081 timestamp: 0,
1082 checksum: 0,
1083 };
1084 let mut enc = entry.encode();
1085 let flip_pos = enc.len() - 10; enc[flip_pos] ^= 0xFF;
1088
1089 let result = WalEntry::decode(&enc, 0);
1090 assert!(matches!(result, Err(WalError::ChecksumMismatch { .. })));
1091 }
1092
1093 #[test]
1094 fn test_invalid_magic_detected() {
1095 let entry = WalEntry {
1096 seq_num: 2,
1097 tx_id: 0,
1098 op_type: WalOpType::Put,
1099 key: b"k".to_vec(),
1100 value: b"v".to_vec(),
1101 timestamp: 0,
1102 checksum: 0,
1103 };
1104 let mut enc = entry.encode();
1105 enc[0] = b'Z'; let result = WalEntry::decode(&enc, 0);
1108 assert!(matches!(result, Err(WalError::InvalidMagic)));
1109 }
1110
1111 #[test]
1112 fn test_corrupted_entry_too_short() {
1113 let data = vec![b'W', b'A', b'L', b'X', 0x01]; let result = WalEntry::decode(&data, 0);
1115 assert!(matches!(result, Err(WalError::CorruptedEntry(_))));
1116 }
1117
1118 #[test]
1119 fn test_corrupted_entry_truncated_key() {
1120 let entry = WalEntry {
1121 seq_num: 5,
1122 tx_id: 0,
1123 op_type: WalOpType::Put,
1124 key: b"longkey12345678".to_vec(),
1125 value: b"val".to_vec(),
1126 timestamp: 0,
1127 checksum: 0,
1128 };
1129 let mut enc = entry.encode();
1130 enc.truncate(enc.len() / 2); let result = WalEntry::decode(&enc, 0);
1132 assert!(result.is_err());
1133 }
1134
1135 #[test]
1138 fn test_write_returns_sequential_seq_nums() {
1139 let mut wal = default_wal();
1140 let s1 = wal
1141 .write(b"k1".to_vec(), b"v1".to_vec(), WalOpType::Put)
1142 .unwrap();
1143 let s2 = wal
1144 .write(b"k2".to_vec(), b"v2".to_vec(), WalOpType::Put)
1145 .unwrap();
1146 let s3 = wal
1147 .write(b"k3".to_vec(), b"v3".to_vec(), WalOpType::Delete)
1148 .unwrap();
1149 assert!(s1 < s2);
1150 assert!(s2 < s3);
1151 }
1152
1153 #[test]
1154 fn test_write_increases_entry_count() {
1155 let mut wal = default_wal();
1156 assert_eq!(wal.entry_count(), 0);
1157 wal.write(b"a".to_vec(), b"b".to_vec(), WalOpType::Put)
1158 .unwrap();
1159 assert_eq!(wal.entry_count(), 1);
1160 wal.write(b"c".to_vec(), b"d".to_vec(), WalOpType::Put)
1161 .unwrap();
1162 assert_eq!(wal.entry_count(), 2);
1163 }
1164
1165 #[test]
1166 fn test_write_segment_not_empty() {
1167 let mut wal = default_wal();
1168 wal.write(b"key".to_vec(), b"val".to_vec(), WalOpType::Put)
1169 .unwrap();
1170 assert!(!wal.export_segment().is_empty());
1171 }
1172
1173 #[test]
1174 fn test_stats_tracks_entries_and_bytes() {
1175 let mut wal = default_wal();
1176 wal.write(b"x".to_vec(), b"y".to_vec(), WalOpType::Put)
1177 .unwrap();
1178 wal.write(b"x".to_vec(), Vec::new(), WalOpType::Delete)
1179 .unwrap();
1180 let s = wal.stats();
1181 assert_eq!(s.total_entries, 2);
1182 assert!(s.total_bytes > 0);
1183 }
1184
1185 #[test]
1188 fn test_begin_transaction_returns_unique_ids() {
1189 let mut wal = default_wal();
1190 let t1 = wal.begin_transaction();
1191 let t2 = wal.begin_transaction();
1192 let t3 = wal.begin_transaction();
1193 assert_ne!(t1, t2);
1194 assert_ne!(t2, t3);
1195 }
1196
1197 #[test]
1198 fn test_write_tx_valid_transaction() {
1199 let mut wal = default_wal();
1200 let tx = wal.begin_transaction();
1201 let seq = wal
1202 .write_tx(tx, b"key".to_vec(), b"value".to_vec(), WalOpType::Put)
1203 .unwrap();
1204 assert!(seq > 0);
1205 }
1206
1207 #[test]
1208 fn test_write_tx_unknown_transaction_errors() {
1209 let mut wal = default_wal();
1210 let result = wal.write_tx(9999, b"k".to_vec(), b"v".to_vec(), WalOpType::Put);
1211 assert!(matches!(result, Err(WalError::TransactionNotFound(9999))));
1212 }
1213
1214 #[test]
1215 fn test_commit_transaction_succeeds() {
1216 let mut wal = default_wal();
1217 let tx = wal.begin_transaction();
1218 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1219 .unwrap();
1220 wal.commit_transaction(tx).unwrap();
1221 }
1222
1223 #[test]
1224 fn test_commit_unknown_transaction_errors() {
1225 let mut wal = default_wal();
1226 let result = wal.commit_transaction(42);
1227 assert!(matches!(result, Err(WalError::TransactionNotFound(42))));
1228 }
1229
1230 #[test]
1231 fn test_rollback_transaction_succeeds() {
1232 let mut wal = default_wal();
1233 let tx = wal.begin_transaction();
1234 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1235 .unwrap();
1236 wal.rollback_transaction(tx).unwrap();
1237 }
1238
1239 #[test]
1240 fn test_rollback_unknown_transaction_errors() {
1241 let mut wal = default_wal();
1242 let result = wal.rollback_transaction(77);
1243 assert!(matches!(result, Err(WalError::TransactionNotFound(77))));
1244 }
1245
1246 #[test]
1247 fn test_double_commit_errors() {
1248 let mut wal = default_wal();
1249 let tx = wal.begin_transaction();
1250 wal.commit_transaction(tx).unwrap();
1251 let result = wal.commit_transaction(tx);
1253 assert!(result.is_err());
1254 }
1255
1256 #[test]
1257 fn test_double_rollback_errors() {
1258 let mut wal = default_wal();
1259 let tx = wal.begin_transaction();
1260 wal.rollback_transaction(tx).unwrap();
1261 let result = wal.rollback_transaction(tx);
1262 assert!(result.is_err());
1263 }
1264
1265 #[test]
1266 fn test_active_transactions_count_decreases_after_commit() {
1267 let mut wal = default_wal();
1268 let t1 = wal.begin_transaction();
1269 let t2 = wal.begin_transaction();
1270 assert_eq!(wal.stats().active_transactions, 2);
1271 wal.commit_transaction(t1).unwrap();
1272 assert_eq!(wal.stats().active_transactions, 1);
1273 wal.rollback_transaction(t2).unwrap();
1274 assert_eq!(wal.stats().active_transactions, 0);
1275 }
1276
1277 #[test]
1280 fn test_checkpoint_returns_seq_num() {
1281 let mut wal = default_wal();
1282 wal.write(b"a".to_vec(), b"b".to_vec(), WalOpType::Put)
1283 .unwrap();
1284 let cp_seq = wal.checkpoint().unwrap();
1285 assert!(cp_seq > 0);
1286 assert_eq!(wal.stats().last_checkpoint_seq, cp_seq);
1287 }
1288
1289 #[test]
1290 fn test_checkpoint_truncates_old_entries() {
1291 let mut wal = default_wal();
1292 for i in 0u8..50 {
1293 wal.write(vec![i], vec![i], WalOpType::Put).unwrap();
1294 }
1295 let before = wal.entry_count();
1296 wal.checkpoint().unwrap();
1297 for i in 50u8..100 {
1299 wal.write(vec![i], vec![i], WalOpType::Put).unwrap();
1300 }
1301 wal.checkpoint().unwrap();
1302 let after = wal.entry_count();
1303 assert!(after < before + 51); }
1306
1307 #[test]
1308 fn test_checkpoint_preserves_active_tx_entries() {
1309 let mut wal = default_wal();
1310 let tx = wal.begin_transaction();
1311 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1312 .unwrap();
1313 wal.checkpoint().unwrap();
1314 assert!(wal.entries.iter().any(|e| e.tx_id == tx));
1316 }
1317
1318 #[test]
1319 fn test_auto_checkpoint_triggers() {
1320 let config = WalConfig {
1321 checkpoint_interval: 3,
1322 ..WalConfig::default()
1323 };
1324 let mut wal = WalWriteAheadLog::new(config);
1325 for i in 0u8..3 {
1326 wal.write(vec![i], vec![i], WalOpType::Put).unwrap();
1327 }
1328 assert_eq!(wal.writes_since_checkpoint, 0);
1330 assert!(wal.last_checkpoint_seq > 0);
1331 }
1332
1333 #[test]
1336 fn test_export_segment_contains_all_entries() {
1337 let mut wal = default_wal();
1338 wal.write(b"k1".to_vec(), b"v1".to_vec(), WalOpType::Put)
1339 .unwrap();
1340 wal.write(b"k2".to_vec(), b"v2".to_vec(), WalOpType::Delete)
1341 .unwrap();
1342 let seg = wal.export_segment();
1343 let mut pos = 0usize;
1345 let mut count = 0usize;
1346 while pos < seg.len() {
1347 let (_, consumed) = WalEntry::decode(&seg, pos).unwrap();
1348 pos += consumed;
1349 count += 1;
1350 }
1351 assert_eq!(count, 2);
1352 }
1353
1354 #[test]
1355 fn test_export_segment_is_deterministic() {
1356 let mut wal = default_wal();
1357 wal.write(b"key".to_vec(), b"val".to_vec(), WalOpType::Put)
1358 .unwrap();
1359 let a = wal.export_segment();
1360 let b = wal.export_segment();
1361 assert_eq!(a, b);
1362 }
1363
1364 #[test]
1367 fn test_recover_clean_log() {
1368 let mut wal = default_wal();
1369 wal.write(b"a".to_vec(), b"1".to_vec(), WalOpType::Put)
1370 .unwrap();
1371 wal.write(b"b".to_vec(), b"2".to_vec(), WalOpType::Put)
1372 .unwrap();
1373 wal.write(b"c".to_vec(), Vec::new(), WalOpType::Delete)
1374 .unwrap();
1375 let seg = wal.export_segment();
1376
1377 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1378 assert_eq!(rr.entries_recovered, 3);
1379 assert_eq!(rr.entries_replayed, 3); assert_eq!(rr.partial_tx_discarded, 0);
1381 assert_eq!(rr.transactions_recovered, 0);
1382 assert!(rr.last_seq_num >= 3);
1383 }
1384
1385 #[test]
1386 fn test_recover_committed_transaction() {
1387 let mut wal = default_wal();
1388 let tx = wal.begin_transaction();
1389 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1390 .unwrap();
1391 wal.commit_transaction(tx).unwrap();
1392 let seg = wal.export_segment();
1393
1394 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1395 assert_eq!(rr.entries_replayed, 1); assert_eq!(rr.partial_tx_discarded, 0);
1397 assert_eq!(rr.transactions_recovered, 1);
1398 }
1399
1400 #[test]
1401 fn test_recover_partial_tx_discarded() {
1402 let mut wal = default_wal();
1403 let tx = wal.begin_transaction();
1404 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1405 .unwrap();
1406 let seg = wal.export_segment();
1408
1409 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1410 assert_eq!(rr.entries_replayed, 0); assert!(rr.partial_tx_discarded > 0);
1412 }
1413
1414 #[test]
1415 fn test_recover_rolled_back_tx_not_replayed() {
1416 let mut wal = default_wal();
1417 let tx = wal.begin_transaction();
1418 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1419 .unwrap();
1420 wal.rollback_transaction(tx).unwrap();
1421 let seg = wal.export_segment();
1422
1423 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1424 assert_eq!(rr.entries_replayed, 0);
1425 assert_eq!(rr.partial_tx_discarded, 0);
1427 }
1428
1429 #[test]
1430 fn test_recover_mixed_committed_and_partial() {
1431 let mut wal = default_wal();
1432 let t1 = wal.begin_transaction();
1434 wal.write_tx(t1, b"k1".to_vec(), b"v1".to_vec(), WalOpType::Put)
1435 .unwrap();
1436 wal.commit_transaction(t1).unwrap();
1437 let t2 = wal.begin_transaction();
1439 wal.write_tx(t2, b"k2".to_vec(), b"v2".to_vec(), WalOpType::Put)
1440 .unwrap();
1441 wal.write_tx(t2, b"k3".to_vec(), b"v3".to_vec(), WalOpType::Put)
1442 .unwrap();
1443 let seg = wal.export_segment();
1444
1445 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1446 assert_eq!(rr.entries_replayed, 1); assert!(rr.partial_tx_discarded >= 2); assert_eq!(rr.transactions_recovered, 2);
1449 }
1450
1451 #[test]
1452 fn test_recover_empty_buffer() {
1453 let rr = WalWriteAheadLog::recover(&[]).unwrap();
1454 assert_eq!(rr.entries_recovered, 0);
1455 assert_eq!(rr.last_seq_num, 0);
1456 }
1457
1458 #[test]
1459 fn test_recover_garbage_prefix_skipped() {
1460 let mut wal = default_wal();
1461 wal.write(b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1462 .unwrap();
1463 let mut seg = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00];
1464 seg.extend_from_slice(&wal.export_segment());
1465
1466 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1467 assert_eq!(rr.entries_recovered, 1);
1468 }
1469
1470 #[test]
1471 fn test_recover_last_seq_num_correct() {
1472 let mut wal = default_wal();
1473 for i in 0u8..10 {
1474 wal.write(vec![i], vec![i], WalOpType::Put).unwrap();
1475 }
1476 let seg = wal.export_segment();
1477 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1478 assert_eq!(rr.last_seq_num, 10);
1479 }
1480
1481 #[test]
1484 fn test_replay_standalone_entries() {
1485 let mut wal = default_wal();
1486 wal.write(b"k1".to_vec(), b"v1".to_vec(), WalOpType::Put)
1487 .unwrap();
1488 wal.write(b"k2".to_vec(), b"v2".to_vec(), WalOpType::Put)
1489 .unwrap();
1490 wal.write(b"k3".to_vec(), Vec::new(), WalOpType::Delete)
1491 .unwrap();
1492
1493 let entries: Vec<WalEntry> = wal.entries.clone();
1494 let mut replayed = Vec::new();
1495 let count = WalWriteAheadLog::replay(&entries, &mut |e| {
1496 replayed.push(e.clone());
1497 });
1498 assert_eq!(count, 3);
1499 assert_eq!(replayed.len(), 3);
1500 }
1501
1502 #[test]
1503 fn test_replay_skips_control_entries() {
1504 let mut wal = default_wal();
1505 wal.write(b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1506 .unwrap();
1507 wal.checkpoint().unwrap(); let entries: Vec<WalEntry> = wal.entries.clone();
1510 let mut count_called = 0usize;
1511 WalWriteAheadLog::replay(&entries, &mut |_| {
1512 count_called += 1;
1513 });
1514 assert_eq!(count_called, 1);
1516 }
1517
1518 #[test]
1519 fn test_replay_skips_uncommitted_tx() {
1520 let mut wal = default_wal();
1521 let tx = wal.begin_transaction();
1522 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1523 .unwrap();
1524 let entries: Vec<WalEntry> = wal.entries.clone();
1527 let mut count_called = 0usize;
1528 WalWriteAheadLog::replay(&entries, &mut |_| {
1529 count_called += 1;
1530 });
1531 assert_eq!(count_called, 0);
1532 }
1533
1534 #[test]
1535 fn test_replay_includes_committed_tx() {
1536 let mut wal = default_wal();
1537 let tx = wal.begin_transaction();
1538 wal.write_tx(tx, b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1539 .unwrap();
1540 wal.write_tx(tx, b"k2".to_vec(), Vec::new(), WalOpType::Delete)
1541 .unwrap();
1542 wal.commit_transaction(tx).unwrap();
1543
1544 let entries: Vec<WalEntry> = wal.entries.clone();
1545 let mut seen_keys: Vec<Vec<u8>> = Vec::new();
1546 WalWriteAheadLog::replay(&entries, &mut |e| {
1547 seen_keys.push(e.key.clone());
1548 });
1549 assert_eq!(seen_keys.len(), 2);
1550 assert!(seen_keys.contains(&b"k".to_vec()));
1551 assert!(seen_keys.contains(&b"k2".to_vec()));
1552 }
1553
1554 #[test]
1555 fn test_replay_order_preserved() {
1556 let mut wal = default_wal();
1557 let keys: &[&[u8]] = &[b"alpha", b"beta_", b"gamma", b"delta"];
1558 for k in keys {
1559 wal.write(k.to_vec(), b"v".to_vec(), WalOpType::Put)
1560 .unwrap();
1561 }
1562 let entries: Vec<WalEntry> = wal.entries.clone();
1563 let mut order: Vec<Vec<u8>> = Vec::new();
1564 WalWriteAheadLog::replay(&entries, &mut |e| {
1565 order.push(e.key.clone());
1566 });
1567 for (i, k) in keys.iter().enumerate() {
1568 assert_eq!(order[i], k.to_vec());
1569 }
1570 }
1571
1572 #[test]
1573 fn test_replay_empty_entries() {
1574 let count = WalWriteAheadLog::replay(&[], &mut |_| {});
1575 assert_eq!(count, 0);
1576 }
1577
1578 #[test]
1581 fn test_truncate_before_removes_entries() {
1582 let mut wal = default_wal();
1583 for i in 1u8..=10 {
1584 wal.write(vec![i], vec![i], WalOpType::Put).unwrap();
1585 }
1586 assert_eq!(wal.entry_count(), 10);
1587 let removed = wal.truncate_before(6);
1588 assert_eq!(removed, 5); assert_eq!(wal.entry_count(), 5);
1590 }
1591
1592 #[test]
1593 fn test_truncate_before_zero_removes_nothing() {
1594 let mut wal = default_wal();
1595 wal.write(b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1596 .unwrap();
1597 let removed = wal.truncate_before(0);
1598 assert_eq!(removed, 0);
1599 assert_eq!(wal.entry_count(), 1);
1600 }
1601
1602 #[test]
1603 fn test_truncate_before_large_seq_removes_all() {
1604 let mut wal = default_wal();
1605 for i in 0u8..5 {
1606 wal.write(vec![i], vec![i], WalOpType::Put).unwrap();
1607 }
1608 let removed = wal.truncate_before(u64::MAX);
1609 assert_eq!(removed, 5);
1610 assert_eq!(wal.entry_count(), 0);
1611 assert!(wal.export_segment().is_empty());
1612 }
1613
1614 #[test]
1615 fn test_truncate_rebuilds_segment() {
1616 let mut wal = default_wal();
1617 for i in 1u8..=6 {
1618 wal.write(vec![i], vec![i], WalOpType::Put).unwrap();
1619 }
1620 let seg_before = wal.export_segment().len();
1621 wal.truncate_before(4);
1622 let seg_after = wal.export_segment().len();
1623 assert!(seg_after < seg_before);
1624 }
1625
1626 #[test]
1629 fn test_stats_segments_count() {
1630 let wal = default_wal();
1631 assert_eq!(wal.stats().segments_count, 1);
1632 }
1633
1634 #[test]
1635 fn test_stats_last_checkpoint_seq_initial_zero() {
1636 let wal = default_wal();
1637 assert_eq!(wal.stats().last_checkpoint_seq, 0);
1638 }
1639
1640 #[test]
1641 fn test_stats_after_checkpoint() {
1642 let mut wal = default_wal();
1643 wal.write(b"k".to_vec(), b"v".to_vec(), WalOpType::Put)
1644 .unwrap();
1645 let cp = wal.checkpoint().unwrap();
1646 assert_eq!(wal.stats().last_checkpoint_seq, cp);
1647 }
1648
1649 #[test]
1650 fn test_stats_total_bytes_grows() {
1651 let mut wal = default_wal();
1652 let b0 = wal.stats().total_bytes;
1653 wal.write(b"key-large".to_vec(), vec![0u8; 256], WalOpType::Put)
1654 .unwrap();
1655 let b1 = wal.stats().total_bytes;
1656 assert!(b1 > b0);
1657 }
1658
1659 #[test]
1662 fn test_segment_full_on_size_limit() {
1663 let config = WalConfig {
1664 max_segment_size: 100, ..WalConfig::default()
1666 };
1667 let mut wal = WalWriteAheadLog::new(config);
1668 let mut hit_full = false;
1670 for i in 0u8..100 {
1671 let result = wal.write(vec![i], vec![i; 10], WalOpType::Put);
1672 if matches!(result, Err(WalError::SegmentFull)) {
1673 hit_full = true;
1674 break;
1675 }
1676 }
1677 assert!(hit_full, "Expected SegmentFull error to be triggered");
1678 }
1679
1680 #[test]
1681 fn test_segment_full_on_entry_count_limit() {
1682 let config = WalConfig {
1683 max_entries_per_segment: 3,
1684 max_segment_size: 0, checkpoint_interval: 0,
1686 ..WalConfig::default()
1687 };
1688 let mut wal = WalWriteAheadLog::new(config);
1689 wal.write(b"a".to_vec(), b"1".to_vec(), WalOpType::Put)
1690 .unwrap();
1691 wal.write(b"b".to_vec(), b"2".to_vec(), WalOpType::Put)
1692 .unwrap();
1693 wal.write(b"c".to_vec(), b"3".to_vec(), WalOpType::Put)
1694 .unwrap();
1695 let result = wal.write(b"d".to_vec(), b"4".to_vec(), WalOpType::Put);
1696 assert!(matches!(result, Err(WalError::SegmentFull)));
1697 }
1698
1699 #[test]
1702 fn test_multiple_concurrent_transactions() {
1703 let mut wal = default_wal();
1704 let t1 = wal.begin_transaction();
1705 let t2 = wal.begin_transaction();
1706 wal.write_tx(t1, b"t1k1".to_vec(), b"v1".to_vec(), WalOpType::Put)
1707 .unwrap();
1708 wal.write_tx(t2, b"t2k1".to_vec(), b"v2".to_vec(), WalOpType::Put)
1709 .unwrap();
1710 wal.write_tx(t1, b"t1k2".to_vec(), b"v3".to_vec(), WalOpType::Put)
1711 .unwrap();
1712 wal.commit_transaction(t1).unwrap();
1713 wal.rollback_transaction(t2).unwrap();
1714
1715 let entries: Vec<WalEntry> = wal.entries.clone();
1716 let mut replayed_keys: Vec<Vec<u8>> = Vec::new();
1717 WalWriteAheadLog::replay(&entries, &mut |e| {
1718 replayed_keys.push(e.key.clone());
1719 });
1720 assert!(replayed_keys.contains(&b"t1k1".to_vec()));
1722 assert!(replayed_keys.contains(&b"t1k2".to_vec()));
1723 assert!(!replayed_keys.contains(&b"t2k1".to_vec()));
1724 }
1725
1726 #[test]
1729 fn test_sequential_encode_decode_in_buffer() {
1730 let mut buf = Vec::new();
1731 let mut entries = Vec::new();
1732 for i in 0u64..20 {
1733 let e = WalEntry {
1734 seq_num: i + 1,
1735 tx_id: 0,
1736 op_type: WalOpType::Put,
1737 key: format!("key-{i}").into_bytes(),
1738 value: format!("val-{i}").into_bytes(),
1739 timestamp: i * 1000,
1740 checksum: 0,
1741 };
1742 buf.extend_from_slice(&e.encode());
1743 entries.push(e);
1744 }
1745
1746 let mut pos = 0usize;
1747 let mut decoded = Vec::new();
1748 while pos < buf.len() {
1749 let (e, consumed) = WalEntry::decode(&buf, pos).unwrap();
1750 decoded.push(e);
1751 pos += consumed;
1752 }
1753 assert_eq!(decoded.len(), 20);
1754 for (orig, dec) in entries.iter().zip(decoded.iter()) {
1755 assert_eq!(orig.seq_num, dec.seq_num);
1756 assert_eq!(orig.key, dec.key);
1757 assert_eq!(orig.value, dec.value);
1758 }
1759 }
1760
1761 #[test]
1764 fn test_wal_config_defaults_reasonable() {
1765 let cfg = WalConfig::default();
1766 assert!(cfg.max_segment_size > 0);
1767 assert!(cfg.max_entries_per_segment > 0);
1768 assert!(cfg.checkpoint_interval > 0);
1769 }
1770
1771 #[test]
1774 fn test_stress_random_writes_and_recovery() {
1775 let mut rng = Xorshift64::new(42);
1776 let mut wal = default_wal();
1777
1778 for _ in 0..200 {
1779 let key_len = (rng.next() % 64 + 1) as usize;
1780 let val_len = (rng.next() % 256) as usize;
1781 let key = rng.next_bytes(key_len);
1782 let val = rng.next_bytes(val_len);
1783 wal.write(key, val, WalOpType::Put).unwrap();
1784 }
1785
1786 let seg = wal.export_segment();
1787 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1788 assert_eq!(rr.entries_recovered, 200);
1789 assert_eq!(rr.entries_replayed, 200);
1790 }
1791
1792 #[test]
1793 fn test_stress_mixed_transactions_and_standalone() {
1794 let mut rng = Xorshift64::new(7777);
1795 let mut wal = default_wal();
1796 let mut committed_put_count = 0usize;
1797 let mut standalone_count = 0usize;
1798
1799 for _ in 0..10 {
1800 for j in 0u8..5 {
1802 wal.write(vec![j], rng.next_bytes(8), WalOpType::Put)
1803 .unwrap();
1804 standalone_count += 1;
1805 }
1806 let tx = wal.begin_transaction();
1808 for j in 0u8..3 {
1809 wal.write_tx(tx, vec![j], rng.next_bytes(8), WalOpType::Put)
1810 .unwrap();
1811 committed_put_count += 1;
1812 }
1813 wal.commit_transaction(tx).unwrap();
1814 let tx2 = wal.begin_transaction();
1816 wal.write_tx(tx2, b"partial".to_vec(), b"v".to_vec(), WalOpType::Put)
1817 .unwrap();
1818 }
1820
1821 let seg = wal.export_segment();
1822 let rr = WalWriteAheadLog::recover(&seg).unwrap();
1823 assert_eq!(rr.entries_replayed, standalone_count + committed_put_count);
1824 assert!(rr.partial_tx_discarded > 0);
1825 }
1826}