1use std::collections::{HashMap, VecDeque};
8
9#[inline]
15fn fnv1a_32_bytes(data: &[u8]) -> u32 {
16 const OFFSET_BASIS: u32 = 2_166_136_261;
17 const PRIME: u32 = 16_777_619;
18 let mut h = OFFSET_BASIS;
19 for &b in data {
20 h ^= u32::from(b);
21 h = h.wrapping_mul(PRIME);
22 }
23 h
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub enum WalEntryType {
33 Put,
35 Delete,
37 Checkpoint,
39 Begin,
41 Commit,
43 Rollback,
45}
46
47#[derive(Debug, Clone)]
53pub struct WalEntry {
54 pub sequence: u64,
56 pub entry_type: WalEntryType,
58 pub key: String,
60 pub value: Option<Vec<u8>>,
62 pub transaction_id: Option<u64>,
64 pub checksum: u32,
66}
67
68impl WalEntry {
69 pub fn compute_checksum(seq: u64, key: &str, value: &Option<Vec<u8>>) -> u32 {
71 let mut buf: Vec<u8> =
72 Vec::with_capacity(8 + key.len() + value.as_deref().map_or(0, |v| v.len()));
73 buf.extend_from_slice(&seq.to_le_bytes());
74 buf.extend_from_slice(key.as_bytes());
75 if let Some(v) = value {
76 buf.extend_from_slice(v);
77 }
78 fnv1a_32_bytes(&buf)
79 }
80
81 pub fn is_valid(&self) -> bool {
83 self.checksum == Self::compute_checksum(self.sequence, &self.key, &self.value)
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum ReplayPolicy {
94 Full,
96 SinceCheckpoint,
98 SinceSequence(u64),
100 LastN(usize),
102}
103
104#[derive(Debug, Clone, Default, PartialEq, Eq)]
110pub struct ReplayStats {
111 pub entries_read: usize,
113 pub entries_applied: usize,
115 pub entries_skipped: usize,
117 pub checkpoints_found: usize,
119 pub invalid_entries: usize,
121 pub final_sequence: u64,
123}
124
125#[derive(Debug, Clone, Default)]
131pub struct ReplayState {
132 pub store: HashMap<String, Vec<u8>>,
134 pub sequence: u64,
136 pub active_transactions: HashMap<u64, Vec<WalEntry>>,
138 pub committed_count: u64,
140 pub rolled_back_count: u64,
142}
143
144#[derive(Debug, Clone, Default, PartialEq, Eq)]
150pub struct WalStats {
151 pub total_entries: usize,
153 pub checkpoint_count: usize,
155 pub oldest_sequence: u64,
157 pub newest_sequence: u64,
159 pub estimated_replay_size: usize,
161}
162
163pub struct StorageWALReplay {
174 entries: VecDeque<WalEntry>,
176 max_entries: usize,
178 next_sequence: u64,
180 checkpoint_sequences: Vec<u64>,
182}
183
184impl StorageWALReplay {
185 pub fn new(max_entries: usize) -> Self {
187 Self {
188 entries: VecDeque::new(),
189 max_entries: max_entries.max(1),
190 next_sequence: 1,
191 checkpoint_sequences: Vec::new(),
192 }
193 }
194
195 pub fn append(
207 &mut self,
208 entry_type: WalEntryType,
209 key: String,
210 value: Option<Vec<u8>>,
211 tx_id: Option<u64>,
212 ) -> WalEntry {
213 let seq = self.next_sequence;
214 self.next_sequence += 1;
215
216 let checksum = WalEntry::compute_checksum(seq, &key, &value);
217 let is_checkpoint = entry_type == WalEntryType::Checkpoint;
218
219 let entry = WalEntry {
220 sequence: seq,
221 entry_type,
222 key,
223 value,
224 transaction_id: tx_id,
225 checksum,
226 };
227
228 if self.entries.len() >= self.max_entries {
230 if let Some(evicted) = self.entries.pop_front() {
231 self.checkpoint_sequences.retain(|&s| s != evicted.sequence);
233 }
234 }
235
236 if is_checkpoint {
237 self.checkpoint_sequences.push(seq);
238 }
239
240 self.entries.push_back(entry.clone());
241 entry
242 }
243
244 pub fn replay(&self, policy: ReplayPolicy) -> (ReplayState, ReplayStats) {
252 let mut stats = ReplayStats::default();
253 let mut state = ReplayState::default();
254
255 let total = self.entries.len();
258 let window_entries: Vec<&WalEntry> = match policy {
259 ReplayPolicy::Full => {
260 stats.entries_read = total;
261 self.entries.iter().collect()
262 }
263 ReplayPolicy::SinceCheckpoint => {
264 let start_seq = self
266 .checkpoint_sequences
267 .iter()
268 .copied()
269 .rev()
270 .find(|&s| self.entries.iter().any(|e| e.sequence == s))
271 .unwrap_or(0);
272 let skip_count = self
273 .entries
274 .iter()
275 .filter(|e| e.sequence < start_seq)
276 .count();
277 stats.entries_skipped = skip_count;
278 stats.entries_read = total.saturating_sub(skip_count);
279 self.entries
280 .iter()
281 .filter(|e| e.sequence >= start_seq)
282 .collect()
283 }
284 ReplayPolicy::SinceSequence(min_seq) => {
285 let skip_count = self.entries.iter().filter(|e| e.sequence < min_seq).count();
286 stats.entries_skipped = skip_count;
287 stats.entries_read = total.saturating_sub(skip_count);
288 self.entries
289 .iter()
290 .filter(|e| e.sequence >= min_seq)
291 .collect()
292 }
293 ReplayPolicy::LastN(n) => {
294 let skip_count = total.saturating_sub(n);
295 let take = total.min(n);
296 stats.entries_skipped = skip_count;
297 stats.entries_read = take;
298 self.entries.iter().skip(skip_count).take(take).collect()
299 }
300 };
301
302 for entry in window_entries {
303 if !entry.is_valid() {
305 stats.invalid_entries += 1;
306 continue;
307 }
308
309 match &entry.entry_type {
310 WalEntryType::Checkpoint => {
311 stats.checkpoints_found += 1;
312 stats.entries_applied += 1;
313 state.sequence = entry.sequence;
314 stats.final_sequence = entry.sequence;
315 }
316 WalEntryType::Begin => {
317 if let Some(tx_id) = entry.transaction_id {
318 state.active_transactions.entry(tx_id).or_default();
319 }
320 stats.entries_applied += 1;
321 state.sequence = entry.sequence;
322 stats.final_sequence = entry.sequence;
323 }
324 WalEntryType::Commit => {
325 if let Some(tx_id) = entry.transaction_id {
326 if let Some(buffered) = state.active_transactions.remove(&tx_id) {
327 for buffered_entry in buffered {
328 match buffered_entry.entry_type {
329 WalEntryType::Put => {
330 if let Some(v) = buffered_entry.value {
331 state.store.insert(buffered_entry.key, v);
332 }
333 }
334 WalEntryType::Delete => {
335 state.store.remove(&buffered_entry.key);
336 }
337 _ => {}
338 }
339 }
340 state.committed_count += 1;
341 }
342 }
343 stats.entries_applied += 1;
344 state.sequence = entry.sequence;
345 stats.final_sequence = entry.sequence;
346 }
347 WalEntryType::Rollback => {
348 if let Some(tx_id) = entry.transaction_id {
349 state.active_transactions.remove(&tx_id);
350 state.rolled_back_count += 1;
351 }
352 stats.entries_applied += 1;
353 state.sequence = entry.sequence;
354 stats.final_sequence = entry.sequence;
355 }
356 WalEntryType::Put => {
357 if let Some(tx_id) = entry.transaction_id {
358 state
359 .active_transactions
360 .entry(tx_id)
361 .or_default()
362 .push(entry.clone());
363 } else if let Some(v) = &entry.value {
364 state.store.insert(entry.key.clone(), v.clone());
365 }
366 stats.entries_applied += 1;
367 state.sequence = entry.sequence;
368 stats.final_sequence = entry.sequence;
369 }
370 WalEntryType::Delete => {
371 if let Some(tx_id) = entry.transaction_id {
372 state
373 .active_transactions
374 .entry(tx_id)
375 .or_default()
376 .push(entry.clone());
377 } else {
378 state.store.remove(&entry.key);
379 }
380 stats.entries_applied += 1;
381 state.sequence = entry.sequence;
382 stats.final_sequence = entry.sequence;
383 }
384 }
385 }
386
387 (state, stats)
388 }
389
390 pub fn verify_entries(&self) -> (usize, usize) {
398 let mut valid = 0usize;
399 let mut invalid = 0usize;
400 for entry in &self.entries {
401 if entry.is_valid() {
402 valid += 1;
403 } else {
404 invalid += 1;
405 }
406 }
407 (valid, invalid)
408 }
409
410 pub fn last_checkpoint_sequence(&self) -> Option<u64> {
416 self.checkpoint_sequences.last().copied()
417 }
418
419 pub fn truncate_before(&mut self, sequence: u64) -> usize {
427 let before = self.entries.len();
428 self.entries.retain(|e| e.sequence >= sequence);
429 self.checkpoint_sequences.retain(|&s| s >= sequence);
430 before - self.entries.len()
431 }
432
433 pub fn wal_stats(&self) -> WalStats {
439 let total_entries = self.entries.len();
440 let checkpoint_count = self
441 .entries
442 .iter()
443 .filter(|e| e.entry_type == WalEntryType::Checkpoint)
444 .count();
445
446 let oldest_sequence = self.entries.front().map_or(0, |e| e.sequence);
447 let newest_sequence = self.entries.back().map_or(0, |e| e.sequence);
448
449 let estimated_replay_size = self
450 .entries
451 .iter()
452 .map(|e| {
453 32usize + e.key.len() + e.value.as_deref().map_or(0, |v| v.len())
455 })
456 .sum();
457
458 WalStats {
459 total_entries,
460 checkpoint_count,
461 oldest_sequence,
462 newest_sequence,
463 estimated_replay_size,
464 }
465 }
466
467 pub fn len(&self) -> usize {
473 self.entries.len()
474 }
475
476 pub fn is_empty(&self) -> bool {
478 self.entries.is_empty()
479 }
480
481 pub fn next_sequence(&self) -> u64 {
483 self.next_sequence
484 }
485}
486
487#[cfg(test)]
492mod tests {
493 use super::{ReplayPolicy, StorageWALReplay, WalEntry, WalEntryType};
494
495 fn make_replay(max: usize) -> StorageWALReplay {
500 StorageWALReplay::new(max)
501 }
502
503 fn put(log: &mut StorageWALReplay, key: &str, val: &[u8]) -> WalEntry {
504 log.append(WalEntryType::Put, key.to_string(), Some(val.to_vec()), None)
505 }
506
507 fn delete(log: &mut StorageWALReplay, key: &str) -> WalEntry {
508 log.append(WalEntryType::Delete, key.to_string(), None, None)
509 }
510
511 fn checkpoint(log: &mut StorageWALReplay) -> WalEntry {
512 log.append(WalEntryType::Checkpoint, String::new(), None, None)
513 }
514
515 fn begin(log: &mut StorageWALReplay, tx: u64) -> WalEntry {
516 log.append(WalEntryType::Begin, String::new(), None, Some(tx))
517 }
518
519 fn commit(log: &mut StorageWALReplay, tx: u64) -> WalEntry {
520 log.append(WalEntryType::Commit, String::new(), None, Some(tx))
521 }
522
523 fn rollback(log: &mut StorageWALReplay, tx: u64) -> WalEntry {
524 log.append(WalEntryType::Rollback, String::new(), None, Some(tx))
525 }
526
527 fn tx_put(log: &mut StorageWALReplay, key: &str, val: &[u8], tx: u64) -> WalEntry {
528 log.append(
529 WalEntryType::Put,
530 key.to_string(),
531 Some(val.to_vec()),
532 Some(tx),
533 )
534 }
535
536 fn tx_delete(log: &mut StorageWALReplay, key: &str, tx: u64) -> WalEntry {
537 log.append(WalEntryType::Delete, key.to_string(), None, Some(tx))
538 }
539
540 #[test]
545 fn test_new_is_empty() {
546 let log = make_replay(100);
547 assert!(log.is_empty());
548 assert_eq!(log.len(), 0);
549 assert_eq!(log.next_sequence(), 1);
550 }
551
552 #[test]
557 fn test_sequence_increments() {
558 let mut log = make_replay(100);
559 let e1 = put(&mut log, "a", b"1");
560 let e2 = put(&mut log, "b", b"2");
561 let e3 = put(&mut log, "c", b"3");
562 assert_eq!(e1.sequence, 1);
563 assert_eq!(e2.sequence, 2);
564 assert_eq!(e3.sequence, 3);
565 assert_eq!(log.next_sequence(), 4);
566 }
567
568 #[test]
573 fn test_checksum_valid_on_append() {
574 let mut log = make_replay(100);
575 let entry = put(&mut log, "key", b"value");
576 assert!(entry.is_valid());
577 }
578
579 #[test]
580 fn test_checksum_detects_tampering() {
581 let mut entry = WalEntry {
582 sequence: 1,
583 entry_type: WalEntryType::Put,
584 key: "k".to_string(),
585 value: Some(b"v".to_vec()),
586 transaction_id: None,
587 checksum: WalEntry::compute_checksum(1, "k", &Some(b"v".to_vec())),
588 };
589 assert!(entry.is_valid());
590 entry.key = "tampered".to_string();
592 assert!(!entry.is_valid());
593 }
594
595 #[test]
596 fn test_checksum_value_none_vs_some() {
597 let cs_none = WalEntry::compute_checksum(1, "k", &None);
598 let cs_some = WalEntry::compute_checksum(1, "k", &Some(b"data".to_vec()));
599 assert_ne!(cs_none, cs_some);
600 }
601
602 #[test]
607 fn test_verify_all_valid() {
608 let mut log = make_replay(100);
609 put(&mut log, "a", b"1");
610 put(&mut log, "b", b"2");
611 let (valid, invalid) = log.verify_entries();
612 assert_eq!(valid, 2);
613 assert_eq!(invalid, 0);
614 }
615
616 #[test]
621 fn test_full_replay_puts() {
622 let mut log = make_replay(100);
623 put(&mut log, "x", b"hello");
624 put(&mut log, "y", b"world");
625 let (state, stats) = log.replay(ReplayPolicy::Full);
626 assert_eq!(
627 state.store.get("x").map(|v| v.as_slice()),
628 Some(b"hello".as_ref())
629 );
630 assert_eq!(
631 state.store.get("y").map(|v| v.as_slice()),
632 Some(b"world".as_ref())
633 );
634 assert_eq!(stats.entries_applied, 2);
635 assert_eq!(stats.entries_skipped, 0);
636 assert_eq!(stats.invalid_entries, 0);
637 assert_eq!(stats.final_sequence, 2);
638 }
639
640 #[test]
645 fn test_full_replay_delete_removes_key() {
646 let mut log = make_replay(100);
647 put(&mut log, "k", b"val");
648 delete(&mut log, "k");
649 let (state, stats) = log.replay(ReplayPolicy::Full);
650 assert!(!state.store.contains_key("k"));
651 assert_eq!(stats.entries_applied, 2);
652 }
653
654 #[test]
659 fn test_full_replay_overwrite() {
660 let mut log = make_replay(100);
661 put(&mut log, "k", b"v1");
662 put(&mut log, "k", b"v2");
663 let (state, _) = log.replay(ReplayPolicy::Full);
664 assert_eq!(state.store["k"], b"v2");
665 }
666
667 #[test]
672 fn test_since_checkpoint_skips_pre_checkpoint() {
673 let mut log = make_replay(100);
674 put(&mut log, "before", b"old");
675 checkpoint(&mut log);
676 put(&mut log, "after", b"new");
677 let (state, stats) = log.replay(ReplayPolicy::SinceCheckpoint);
678 assert!(!state.store.contains_key("before"));
682 assert!(state.store.contains_key("after"));
683 assert_eq!(stats.entries_skipped, 1);
684 assert_eq!(stats.checkpoints_found, 1);
685 }
686
687 #[test]
692 fn test_since_checkpoint_no_checkpoint_replays_all() {
693 let mut log = make_replay(100);
694 put(&mut log, "a", b"1");
695 put(&mut log, "b", b"2");
696 let (state, stats) = log.replay(ReplayPolicy::SinceCheckpoint);
698 assert_eq!(state.store.len(), 2);
699 assert_eq!(stats.entries_skipped, 0);
700 }
701
702 #[test]
707 fn test_since_sequence_skips_older() {
708 let mut log = make_replay(100);
709 put(&mut log, "a", b"1"); put(&mut log, "b", b"2"); put(&mut log, "c", b"3"); let (state, stats) = log.replay(ReplayPolicy::SinceSequence(2));
713 assert!(!state.store.contains_key("a"));
714 assert!(state.store.contains_key("b"));
715 assert!(state.store.contains_key("c"));
716 assert_eq!(stats.entries_skipped, 1);
717 assert_eq!(stats.entries_applied, 2);
718 }
719
720 #[test]
725 fn test_last_n_replay() {
726 let mut log = make_replay(100);
727 put(&mut log, "a", b"1");
728 put(&mut log, "b", b"2");
729 put(&mut log, "c", b"3");
730 put(&mut log, "d", b"4");
731 let (state, stats) = log.replay(ReplayPolicy::LastN(2));
732 assert!(!state.store.contains_key("a"));
734 assert!(!state.store.contains_key("b"));
735 assert!(state.store.contains_key("c"));
736 assert!(state.store.contains_key("d"));
737 assert_eq!(stats.entries_skipped, 2);
738 assert_eq!(stats.entries_applied, 2);
739 }
740
741 #[test]
742 fn test_last_n_larger_than_log() {
743 let mut log = make_replay(100);
744 put(&mut log, "a", b"1");
745 put(&mut log, "b", b"2");
746 let (state, stats) = log.replay(ReplayPolicy::LastN(50));
747 assert_eq!(state.store.len(), 2);
748 assert_eq!(stats.entries_skipped, 0);
749 assert_eq!(stats.entries_applied, 2);
750 }
751
752 #[test]
757 fn test_eviction_at_capacity() {
758 let mut log = make_replay(3);
759 put(&mut log, "a", b"1"); put(&mut log, "b", b"2"); put(&mut log, "c", b"3"); put(&mut log, "d", b"4"); assert_eq!(log.len(), 3);
765 let (state, _) = log.replay(ReplayPolicy::Full);
766 assert!(!state.store.contains_key("a"));
767 assert!(state.store.contains_key("b"));
768 assert!(state.store.contains_key("c"));
769 assert!(state.store.contains_key("d"));
770 }
771
772 #[test]
777 fn test_checkpoint_evicted_from_sequences() {
778 let mut log = make_replay(3);
779 checkpoint(&mut log); put(&mut log, "a", b"1");
781 put(&mut log, "b", b"2");
782 put(&mut log, "c", b"3");
784 assert!(log.last_checkpoint_sequence().is_none());
786 }
787
788 #[test]
793 fn test_last_checkpoint_sequence() {
794 let mut log = make_replay(100);
795 assert_eq!(log.last_checkpoint_sequence(), None);
796 checkpoint(&mut log); assert_eq!(log.last_checkpoint_sequence(), Some(1));
798 put(&mut log, "k", b"v");
799 checkpoint(&mut log); assert_eq!(log.last_checkpoint_sequence(), Some(3));
801 }
802
803 #[test]
808 fn test_truncate_before_removes_entries() {
809 let mut log = make_replay(100);
810 put(&mut log, "a", b"1"); put(&mut log, "b", b"2"); put(&mut log, "c", b"3"); let removed = log.truncate_before(2);
814 assert_eq!(removed, 1);
815 assert_eq!(log.len(), 2);
816 }
817
818 #[test]
819 fn test_truncate_before_removes_checkpoint_sequences() {
820 let mut log = make_replay(100);
821 checkpoint(&mut log); put(&mut log, "a", b"1");
823 checkpoint(&mut log); let removed = log.truncate_before(3);
825 assert_eq!(removed, 2); assert_eq!(log.last_checkpoint_sequence(), Some(3));
828 }
829
830 #[test]
831 fn test_truncate_before_zero_removes_nothing() {
832 let mut log = make_replay(100);
833 put(&mut log, "a", b"1");
834 put(&mut log, "b", b"2");
835 let removed = log.truncate_before(0);
836 assert_eq!(removed, 0);
837 assert_eq!(log.len(), 2);
838 }
839
840 #[test]
845 fn test_wal_stats_empty() {
846 let log = make_replay(100);
847 let s = log.wal_stats();
848 assert_eq!(s.total_entries, 0);
849 assert_eq!(s.checkpoint_count, 0);
850 assert_eq!(s.oldest_sequence, 0);
851 assert_eq!(s.newest_sequence, 0);
852 assert_eq!(s.estimated_replay_size, 0);
853 }
854
855 #[test]
856 fn test_wal_stats_with_entries() {
857 let mut log = make_replay(100);
858 put(&mut log, "a", b"hello");
859 checkpoint(&mut log);
860 put(&mut log, "b", b"world");
861 let s = log.wal_stats();
862 assert_eq!(s.total_entries, 3);
863 assert_eq!(s.checkpoint_count, 1);
864 assert_eq!(s.oldest_sequence, 1);
865 assert_eq!(s.newest_sequence, 3);
866 assert!(s.estimated_replay_size > 0);
867 }
868
869 #[test]
874 fn test_transaction_commit_applies_ops() {
875 let mut log = make_replay(100);
876 begin(&mut log, 42);
877 tx_put(&mut log, "k", b"value", 42);
878 commit(&mut log, 42);
879 let (state, stats) = log.replay(ReplayPolicy::Full);
880 assert_eq!(
881 state.store.get("k").map(|v| v.as_slice()),
882 Some(b"value".as_ref())
883 );
884 assert_eq!(state.committed_count, 1);
885 assert!(state.active_transactions.is_empty());
886 assert_eq!(stats.entries_applied, 3);
887 }
888
889 #[test]
894 fn test_transaction_rollback_discards_ops() {
895 let mut log = make_replay(100);
896 begin(&mut log, 7);
897 tx_put(&mut log, "k", b"should_not_appear", 7);
898 rollback(&mut log, 7);
899 let (state, stats) = log.replay(ReplayPolicy::Full);
900 assert!(!state.store.contains_key("k"));
901 assert_eq!(state.rolled_back_count, 1);
902 assert_eq!(stats.entries_applied, 3);
903 }
904
905 #[test]
910 fn test_transaction_commit_delete() {
911 let mut log = make_replay(100);
912 put(&mut log, "k", b"v");
914 begin(&mut log, 1);
915 tx_delete(&mut log, "k", 1);
916 commit(&mut log, 1);
917 let (state, _) = log.replay(ReplayPolicy::Full);
918 assert!(!state.store.contains_key("k"));
919 }
920
921 #[test]
926 fn test_two_concurrent_transactions() {
927 let mut log = make_replay(100);
928 begin(&mut log, 1);
929 begin(&mut log, 2);
930 tx_put(&mut log, "a", b"from-tx1", 1);
931 tx_put(&mut log, "b", b"from-tx2", 2);
932 commit(&mut log, 1);
933 rollback(&mut log, 2);
934 let (state, _) = log.replay(ReplayPolicy::Full);
935 assert_eq!(
936 state.store.get("a").map(|v| v.as_slice()),
937 Some(b"from-tx1".as_ref())
938 );
939 assert!(!state.store.contains_key("b"));
940 assert_eq!(state.committed_count, 1);
941 assert_eq!(state.rolled_back_count, 1);
942 }
943
944 #[test]
949 fn test_open_transaction_remains_in_active() {
950 let mut log = make_replay(100);
951 begin(&mut log, 99);
952 tx_put(&mut log, "k", b"v", 99);
953 let (state, _) = log.replay(ReplayPolicy::Full);
955 assert!(!state.store.contains_key("k"));
956 assert!(state.active_transactions.contains_key(&99));
957 }
958
959 #[test]
964 fn test_invalid_entry_counted_not_applied() {
965 let mut log = make_replay(100);
966 put(&mut log, "valid_key", b"v");
968 log.entries.push_back(WalEntry {
970 sequence: 999,
971 entry_type: WalEntryType::Put,
972 key: "bad_key".to_string(),
973 value: Some(b"data".to_vec()),
974 transaction_id: None,
975 checksum: 0xDEAD_BEEF, });
977 let (state, stats) = log.replay(ReplayPolicy::Full);
978 assert_eq!(stats.invalid_entries, 1);
979 assert!(!state.store.contains_key("bad_key"));
980 assert!(state.store.contains_key("valid_key"));
981 }
982
983 #[test]
988 fn test_replay_empty_log() {
989 let log = make_replay(100);
990 let (state, stats) = log.replay(ReplayPolicy::Full);
991 assert!(state.store.is_empty());
992 assert_eq!(stats.entries_read, 0);
993 assert_eq!(stats.entries_applied, 0);
994 assert_eq!(stats.final_sequence, 0);
995 }
996
997 #[test]
1002 fn test_since_sequence_exact_boundary() {
1003 let mut log = make_replay(100);
1004 put(&mut log, "a", b"1"); put(&mut log, "b", b"2"); let (state, stats) = log.replay(ReplayPolicy::SinceSequence(1));
1008 assert_eq!(state.store.len(), 2);
1009 assert_eq!(stats.entries_skipped, 0);
1010 }
1011
1012 #[test]
1017 fn test_replay_stats_checkpoint_count() {
1018 let mut log = make_replay(100);
1019 checkpoint(&mut log);
1020 put(&mut log, "a", b"1");
1021 checkpoint(&mut log);
1022 let (_, stats) = log.replay(ReplayPolicy::Full);
1023 assert_eq!(stats.checkpoints_found, 2);
1024 }
1025
1026 #[test]
1031 fn test_wal_stats_estimated_size_increases_with_entries() {
1032 let mut log = make_replay(100);
1033 let s0 = log.wal_stats().estimated_replay_size;
1034 put(&mut log, "key", b"some_value");
1035 let s1 = log.wal_stats().estimated_replay_size;
1036 assert!(s1 > s0);
1037 }
1038
1039 #[test]
1044 fn test_since_checkpoint_uses_latest() {
1045 let mut log = make_replay(100);
1046 put(&mut log, "early", b"e"); checkpoint(&mut log); put(&mut log, "mid", b"m"); checkpoint(&mut log); put(&mut log, "late", b"l"); let (state, stats) = log.replay(ReplayPolicy::SinceCheckpoint);
1052 assert!(!state.store.contains_key("early"));
1054 assert!(!state.store.contains_key("mid"));
1055 assert!(state.store.contains_key("late"));
1056 assert_eq!(stats.checkpoints_found, 1); }
1058
1059 #[test]
1064 fn test_last_n_zero() {
1065 let mut log = make_replay(100);
1066 put(&mut log, "a", b"1");
1067 put(&mut log, "b", b"2");
1068 let (state, stats) = log.replay(ReplayPolicy::LastN(0));
1069 assert!(state.store.is_empty());
1070 assert_eq!(stats.entries_applied, 0);
1071 assert_eq!(stats.entries_skipped, 2);
1072 }
1073
1074 #[test]
1079 fn test_truncate_then_replay() {
1080 let mut log = make_replay(100);
1081 put(&mut log, "a", b"1"); put(&mut log, "b", b"2"); put(&mut log, "c", b"3"); log.truncate_before(3);
1085 let (state, stats) = log.replay(ReplayPolicy::Full);
1086 assert!(!state.store.contains_key("a"));
1087 assert!(!state.store.contains_key("b"));
1088 assert!(state.store.contains_key("c"));
1089 assert_eq!(stats.entries_applied, 1);
1090 }
1091
1092 #[test]
1097 fn test_replay_state_sequence_final() {
1098 let mut log = make_replay(100);
1099 put(&mut log, "a", b"1");
1100 put(&mut log, "b", b"2");
1101 put(&mut log, "c", b"3");
1102 let (state, stats) = log.replay(ReplayPolicy::Full);
1103 assert_eq!(state.sequence, 3);
1104 assert_eq!(stats.final_sequence, 3);
1105 }
1106
1107 #[test]
1112 fn test_checkpoint_entry_is_valid() {
1113 let mut log = make_replay(100);
1114 let e = checkpoint(&mut log);
1115 assert_eq!(e.entry_type, WalEntryType::Checkpoint);
1116 assert!(e.is_valid());
1117 }
1118
1119 #[test]
1124 fn test_multiple_puts_last_wins() {
1125 let mut log = make_replay(100);
1126 for i in 0u8..10 {
1127 let val = vec![i];
1128 put(&mut log, "k", &val);
1129 }
1130 let (state, _) = log.replay(ReplayPolicy::Full);
1131 assert_eq!(state.store["k"], vec![9u8]);
1132 }
1133
1134 #[test]
1139 fn test_transaction_multiple_ops_committed() {
1140 let mut log = make_replay(100);
1141 put(&mut log, "keep", b"k");
1143 put(&mut log, "remove", b"r");
1144 begin(&mut log, 1);
1145 tx_put(&mut log, "new_key", b"n", 1);
1146 tx_delete(&mut log, "remove", 1);
1147 commit(&mut log, 1);
1148 let (state, _) = log.replay(ReplayPolicy::Full);
1149 assert!(state.store.contains_key("keep"));
1150 assert!(!state.store.contains_key("remove"));
1151 assert!(state.store.contains_key("new_key"));
1152 }
1153
1154 #[test]
1159 fn test_wal_stats_after_eviction() {
1160 let mut log = make_replay(5);
1161 for i in 0u8..10 {
1162 put(&mut log, "k", &[i]);
1163 }
1164 let s = log.wal_stats();
1165 assert_eq!(s.total_entries, 5);
1166 assert_eq!(s.oldest_sequence, 6);
1168 assert_eq!(s.newest_sequence, 10);
1169 }
1170
1171 #[test]
1176 fn test_checksum_deterministic() {
1177 let v = Some(b"data".to_vec());
1178 let c1 = WalEntry::compute_checksum(42, "mykey", &v);
1179 let c2 = WalEntry::compute_checksum(42, "mykey", &v);
1180 assert_eq!(c1, c2);
1181 }
1182
1183 #[test]
1188 fn test_delete_nonexistent_key_noop() {
1189 let mut log = make_replay(100);
1190 delete(&mut log, "ghost");
1191 let (state, stats) = log.replay(ReplayPolicy::Full);
1192 assert!(state.store.is_empty());
1193 assert_eq!(stats.entries_applied, 1);
1194 }
1195
1196 #[test]
1201 fn test_since_sequence_beyond_newest() {
1202 let mut log = make_replay(100);
1203 put(&mut log, "a", b"1");
1204 put(&mut log, "b", b"2");
1205 let (state, stats) = log.replay(ReplayPolicy::SinceSequence(999));
1206 assert!(state.store.is_empty());
1207 assert_eq!(stats.entries_applied, 0);
1208 assert_eq!(stats.entries_skipped, 2);
1209 }
1210}