Skip to main content

alopex_core/kv/
memory.rs

1//! An in-memory key-value store implementation with Write-Ahead Logging
2//! and Optimistic Concurrency Control for Snapshot Isolation.
3
4use crate::error::{Error, Result};
5#[cfg(feature = "test-hooks")]
6use crate::kv::hooks::{CrashOperation, CrashSimulator, CrashTiming, IoHooks};
7use crate::kv::{KVStore, KVTransaction};
8use crate::log::wal::{WalReader, WalRecord, WalWriter};
9use crate::storage::flush::write_empty_vector_segment;
10use crate::storage::sstable::{SstableReader, SstableWriter};
11use crate::txn::TxnManager;
12use crate::types::{Key, TxnId, TxnMode, TxnState, Value};
13use std::collections::{BTreeMap, HashMap};
14use std::ops::Bound::{Excluded, Included};
15use std::path::{Path, PathBuf};
16use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
17use std::sync::{Arc, RwLock, RwLockReadGuard};
18use tracing::warn;
19
20/// メモリ使用量の統計(バイト単位)。
21#[derive(Debug, Clone, Default, PartialEq)]
22pub struct MemoryStats {
23    /// 全体のメモリ使用量。
24    pub total_bytes: usize,
25    /// KV データのメモリ使用量。
26    pub kv_bytes: usize,
27    /// 補助インデックスのメモリ使用量。
28    pub index_bytes: usize,
29}
30
31/// An in-memory key-value store.
32#[derive(Clone)]
33pub struct MemoryKV {
34    manager: Arc<MemoryTxnManager>,
35}
36
37impl MemoryKV {
38    /// Creates a new, purely transient in-memory KV store.
39    pub fn new() -> Self {
40        Self {
41            manager: Arc::new(MemoryTxnManager::new(None, None, None)),
42        }
43    }
44
45    /// Returns current in-memory usage statistics.
46    pub fn memory_stats(&self) -> MemoryStats {
47        self.manager.memory_stats()
48    }
49
50    /// Creates a new in-memory KV store with an optional memory limit.
51    pub fn new_with_limit(limit: Option<usize>) -> Self {
52        Self {
53            manager: Arc::new(MemoryTxnManager::new_with_limit(limit)),
54        }
55    }
56
57    /// Opens a persistent in-memory KV store from a file path.
58    pub fn open(path: &Path) -> Result<Self> {
59        let wal_writer = WalWriter::new(path)?;
60        let sstable_path = path.with_extension("sst");
61        let manager = Arc::new(MemoryTxnManager::new(
62            Some(wal_writer),
63            Some(path.to_path_buf()),
64            Some(sstable_path),
65        ));
66        manager.recover()?;
67        Ok(Self { manager })
68    }
69
70    /// Opens a persistent KV store with I/O hooks (test only).
71    #[cfg(feature = "test-hooks")]
72    pub fn open_with_io_hooks(path: &Path, hooks: Arc<dyn IoHooks>) -> Result<Self> {
73        let wal_writer = WalWriter::new(path)?;
74        let sstable_path = path.with_extension("sst");
75        let manager = Arc::new(MemoryTxnManager::new(
76            Some(wal_writer),
77            Some(path.to_path_buf()),
78            Some(sstable_path),
79        ));
80        manager.set_io_hooks(Some(hooks));
81        manager.recover()?;
82        Ok(Self { manager })
83    }
84
85    /// Opens a persistent KV store with crash hooks (test only).
86    #[cfg(feature = "test-hooks")]
87    pub fn open_with_crash_hooks(path: &Path, crash_sim: Arc<CrashSimulator>) -> Result<Self> {
88        let wal_writer = WalWriter::new(path)?;
89        let sstable_path = path.with_extension("sst");
90        let manager = Arc::new(MemoryTxnManager::new(
91            Some(wal_writer),
92            Some(path.to_path_buf()),
93            Some(sstable_path),
94        ));
95        manager.set_crash_sim(Some(crash_sim));
96        manager.recover()?;
97        Ok(Self { manager })
98    }
99
100    /// Flushes the in-memory data to an SSTable.
101    pub fn flush(&self) -> Result<()> {
102        self.manager.flush()
103    }
104}
105
106impl Default for MemoryKV {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112impl KVStore for MemoryKV {
113    type Transaction<'a> = MemoryTransaction<'a>;
114    type Manager<'a> = &'a MemoryTxnManager;
115
116    fn txn_manager(&self) -> Self::Manager<'_> {
117        &self.manager
118    }
119
120    fn begin(&self, mode: TxnMode) -> Result<Self::Transaction<'_>> {
121        self.manager.begin_internal(mode)
122    }
123
124    fn runtime_stats(&self) -> Option<crate::kv::RuntimeStats> {
125        Some(crate::kv::RuntimeStats::Memory(self.memory_stats()))
126    }
127
128    fn set_memory_limit_bytes(&self, limit: Option<usize>) -> Result<()> {
129        self.manager.set_memory_limit(limit);
130        Ok(())
131    }
132}
133
134// The internal value stored in the BTreeMap, containing the data and its version.
135type VersionedValue = (Value, u64);
136
137/// The underlying shared state for the in-memory store.
138struct MemorySharedState {
139    /// The main data store, mapping keys to versioned values.
140    data: RwLock<BTreeMap<Key, VersionedValue>>,
141    /// The next transaction ID to be allocated.
142    next_txn_id: AtomicU64,
143    /// The current commit version of the database. Incremented on every successful commit.
144    commit_version: AtomicU64,
145    /// The WAL writer. If None, the store is transient.
146    wal_writer: Option<RwLock<WalWriter>>,
147    /// Optional WAL path for replay on reopen.
148    wal_path: Option<PathBuf>,
149    /// Optional SSTable reader for read-through.
150    sstable: RwLock<Option<SstableReader>>,
151    /// Optional SSTable path for flush/reopen.
152    sstable_path: Option<PathBuf>,
153    /// Optional memory upper limit (bytes) for in-memory mode。
154    memory_limit: RwLock<Option<usize>>,
155    /// Current memory consumption (bytes) tracked across operations。
156    current_memory: AtomicUsize,
157    #[cfg(feature = "test-hooks")]
158    /// Optional I/O hooks for fault injection (test only).
159    io_hooks: RwLock<Option<Arc<dyn IoHooks>>>,
160    #[cfg(feature = "test-hooks")]
161    /// Optional crash simulator for test-only crash points.
162    crash_sim: RwLock<Option<Arc<CrashSimulator>>>,
163}
164
165impl MemorySharedState {
166    /// Check whether adding `additional` bytes would exceed the memory limit.
167    fn check_memory_limit(&self, additional: usize) -> Result<()> {
168        if let Some(limit) = *self.memory_limit.read().unwrap() {
169            let current = self.current_memory.load(Ordering::Relaxed);
170            let requested = current.saturating_add(additional);
171            if requested > limit {
172                return Err(Error::MemoryLimitExceeded { limit, requested });
173            }
174        }
175        Ok(())
176    }
177
178    /// Return current memory usage statistics.
179    fn memory_stats(&self) -> MemoryStats {
180        let kv_bytes = self.current_memory.load(Ordering::Relaxed);
181        MemoryStats {
182            total_bytes: kv_bytes,
183            kv_bytes,
184            index_bytes: 0,
185        }
186    }
187
188    /// Recompute tracked memory usage from existing data (used after recovery).
189    fn recompute_current_memory(&self) {
190        let data = self.data.read().unwrap();
191        let mut total = 0usize;
192        for (k, (v, _)) in data.iter() {
193            total = total.saturating_add(k.len() + v.len());
194        }
195        self.current_memory.store(total, Ordering::Relaxed);
196    }
197}
198
199/// A transaction manager backed by an in-memory map and optional WAL.
200pub struct MemoryTxnManager {
201    state: Arc<MemorySharedState>,
202}
203
204impl MemoryTxnManager {
205    fn new_with_params(
206        wal_writer: Option<WalWriter>,
207        wal_path: Option<PathBuf>,
208        sstable_path: Option<PathBuf>,
209        memory_limit: Option<usize>,
210    ) -> Self {
211        Self {
212            state: Arc::new(MemorySharedState {
213                data: RwLock::new(BTreeMap::new()),
214                next_txn_id: AtomicU64::new(1),
215                commit_version: AtomicU64::new(0),
216                wal_writer: wal_writer.map(RwLock::new),
217                wal_path,
218                sstable: RwLock::new(None),
219                sstable_path,
220                memory_limit: RwLock::new(memory_limit),
221                current_memory: AtomicUsize::new(0),
222                #[cfg(feature = "test-hooks")]
223                io_hooks: RwLock::new(None),
224                #[cfg(feature = "test-hooks")]
225                crash_sim: RwLock::new(None),
226            }),
227        }
228    }
229
230    fn new(
231        wal_writer: Option<WalWriter>,
232        wal_path: Option<PathBuf>,
233        sstable_path: Option<PathBuf>,
234    ) -> Self {
235        Self::new_with_params(wal_writer, wal_path, sstable_path, None)
236    }
237
238    /// Creates an in-memory manager with an optional memory limit.
239    pub fn new_with_limit(limit: Option<usize>) -> Self {
240        Self::new_with_params(None, None, None, limit)
241    }
242
243    #[cfg(feature = "test-hooks")]
244    fn set_io_hooks(&self, hooks: Option<Arc<dyn IoHooks>>) {
245        let mut guard = self.state.io_hooks.write().unwrap();
246        *guard = hooks;
247    }
248
249    #[cfg(feature = "test-hooks")]
250    fn set_crash_sim(&self, crash_sim: Option<Arc<CrashSimulator>>) {
251        let mut guard = self.state.crash_sim.write().unwrap();
252        *guard = crash_sim;
253    }
254
255    #[cfg(feature = "test-hooks")]
256    fn io_hooks(&self) -> Option<Arc<dyn IoHooks>> {
257        self.state.io_hooks.read().unwrap().clone()
258    }
259
260    #[cfg(feature = "test-hooks")]
261    fn crash_sim(&self) -> Option<Arc<CrashSimulator>> {
262        self.state.crash_sim.read().unwrap().clone()
263    }
264
265    /// Returns current memory usage statistics.
266    pub fn memory_stats(&self) -> MemoryStats {
267        self.state.memory_stats()
268    }
269
270    /// Update the configured memory limit at runtime.
271    pub fn set_memory_limit(&self, limit: Option<usize>) {
272        let mut guard = self.state.memory_limit.write().unwrap();
273        *guard = limit;
274    }
275
276    /// Returns a snapshot clone of all key/value pairs.
277    pub fn snapshot(&self) -> Vec<(Key, Value)> {
278        let data = self.state.data.read().unwrap();
279        data.iter()
280            .map(|(k, (v, _))| (k.clone(), v.clone()))
281            .collect()
282    }
283
284    /// Clears all data and resets memory accounting.
285    pub fn clear_all(&self) {
286        let mut data = self.state.data.write().unwrap();
287        data.clear();
288        drop(data);
289        self.state.current_memory.store(0, Ordering::Relaxed);
290        self.state.commit_version.store(0, Ordering::Relaxed);
291    }
292
293    /// Runs compaction if it can fit within the configured memory limit.
294    /// Returns Ok(true) when compaction executed, Ok(false) when skipped.
295    pub fn compact_with_limit<F>(
296        &self,
297        input_bytes: usize,
298        output_bytes: usize,
299        run: F,
300    ) -> Result<bool>
301    where
302        F: FnOnce() -> Result<()>,
303    {
304        if let Some(limit) = *self.state.memory_limit.read().unwrap() {
305            let current = self.state.current_memory.load(Ordering::Relaxed);
306            // predicted usage after compaction: current - input + output (clamped at 0)
307            let prospective = current
308                .saturating_sub(input_bytes)
309                .saturating_add(output_bytes);
310            if prospective > limit {
311                warn!(
312                    limit,
313                    requested = prospective,
314                    "compaction skipped due to memory limit"
315                );
316                return Ok(false);
317            }
318        }
319
320        run()?;
321
322        // Update tracked memory to reflect compaction result.
323        let current = self.state.current_memory.load(Ordering::Relaxed);
324        let new_usage = current
325            .saturating_sub(input_bytes)
326            .saturating_add(output_bytes);
327        self.state
328            .current_memory
329            .store(new_usage, Ordering::Relaxed);
330        Ok(true)
331    }
332
333    #[cfg(feature = "test-hooks")]
334    fn trigger_crash(&self, operation: CrashOperation, timing: CrashTiming) {
335        if let Some(sim) = self.crash_sim() {
336            sim.check_crash(operation, timing);
337        }
338    }
339
340    #[cfg(feature = "test-hooks")]
341    fn notify_wal_hooks(&self, data: &[u8], timing: CrashTiming) -> Result<()> {
342        match timing {
343            CrashTiming::Before => {
344                self.trigger_crash(CrashOperation::WalWrite, CrashTiming::Before);
345                if let Some(hooks) = self.io_hooks() {
346                    hooks.before_wal_write(data).map_err(Error::Io)?;
347                    hooks.before_fsync().map_err(Error::Io)?;
348                }
349                self.trigger_crash(CrashOperation::WalFsync, CrashTiming::Before);
350            }
351            CrashTiming::During => {
352                self.trigger_crash(CrashOperation::WalWrite, CrashTiming::During);
353                self.trigger_crash(CrashOperation::WalFsync, CrashTiming::During);
354            }
355            CrashTiming::After => {
356                self.trigger_crash(CrashOperation::WalWrite, CrashTiming::After);
357                self.trigger_crash(CrashOperation::WalFsync, CrashTiming::After);
358                if let Some(hooks) = self.io_hooks() {
359                    hooks.after_wal_write(data).map_err(Error::Io)?;
360                    hooks.after_fsync().map_err(Error::Io)?;
361                }
362            }
363        }
364        Ok(())
365    }
366
367    #[cfg(feature = "test-hooks")]
368    fn notify_compaction(&self, timing: CrashTiming) {
369        self.trigger_crash(CrashOperation::Compaction, timing);
370        if let Some(hooks) = self.io_hooks() {
371            match timing {
372                CrashTiming::Before => hooks.on_compaction_start(),
373                CrashTiming::After => hooks.on_compaction_end(),
374                CrashTiming::During => {}
375            }
376        }
377    }
378
379    fn append_wal_record(&self, wal: &mut WalWriter, record: &WalRecord) -> Result<()> {
380        #[cfg(feature = "test-hooks")]
381        {
382            let data =
383                bincode::serialize(record).map_err(|e| Error::Io(std::io::Error::other(e)))?;
384            self.notify_wal_hooks(&data, CrashTiming::Before)?;
385            self.notify_wal_hooks(&data, CrashTiming::During)?;
386            wal.append(record)?;
387            self.notify_wal_hooks(&data, CrashTiming::After)?;
388            Ok(())
389        }
390
391        #[cfg(not(feature = "test-hooks"))]
392        {
393            wal.append(record)
394        }
395    }
396
397    fn write_wal(&self, txn_id: TxnId, writes: &BTreeMap<Key, Option<Value>>) -> Result<()> {
398        if let Some(wal_lock) = &self.state.wal_writer {
399            let mut wal = wal_lock.write().unwrap();
400            self.append_wal_record(&mut wal, &WalRecord::Begin(txn_id))?;
401            for (key, value) in writes {
402                let record = match value {
403                    Some(v) => WalRecord::Put(txn_id, key.clone(), v.clone()),
404                    None => WalRecord::Delete(txn_id, key.clone()),
405                };
406                self.append_wal_record(&mut wal, &record)?;
407            }
408            self.append_wal_record(&mut wal, &WalRecord::Commit(txn_id))?;
409            // Durability point: fsync once at the commit boundary so the whole
410            // transaction (Begin..Commit) reaches stable storage together. The
411            // commit is only acknowledged after this returns (CORE-5.1).
412            wal.sync()?;
413        }
414        Ok(())
415    }
416
417    /// In-memory compaction entrypoint that rebuilds the map while honoring memory limits.
418    pub fn compact_in_memory(&self) -> Result<bool> {
419        #[cfg(feature = "test-hooks")]
420        self.notify_compaction(CrashTiming::Before);
421
422        let snapshot_bytes = {
423            let data = self.state.data.read().unwrap();
424            let mut bytes = 0usize;
425            for (k, (v, _)) in data.iter() {
426                bytes = bytes.saturating_add(k.len() + v.len());
427            }
428            bytes
429        };
430
431        let executed = self.compact_with_limit(snapshot_bytes, snapshot_bytes, || {
432            let data = self.state.data.read().unwrap();
433            let mut rebuilt = BTreeMap::new();
434            for (k, (v, version)) in data.iter() {
435                rebuilt.insert(k.clone(), (v.clone(), *version));
436            }
437            drop(data);
438
439            #[cfg(feature = "test-hooks")]
440            self.notify_compaction(CrashTiming::During);
441
442            let mut write_guard = self.state.data.write().unwrap();
443            *write_guard = rebuilt;
444            Ok(())
445        })?;
446
447        #[cfg(feature = "test-hooks")]
448        self.notify_compaction(CrashTiming::After);
449
450        Ok(executed)
451    }
452
453    /// Flushes the current in-memory data to an SSTable file.
454    pub fn flush(&self) -> Result<()> {
455        let Some(path) = self.state.sstable_path.as_ref() else {
456            return Ok(());
457        };
458
459        #[cfg(feature = "test-hooks")]
460        self.notify_compaction(CrashTiming::Before);
461
462        let data = self.state.data.read().unwrap();
463        let mut writer = SstableWriter::create(path)?;
464        for (key, (value, _version)) in data.iter() {
465            #[cfg(feature = "test-hooks")]
466            {
467                let mut record = Vec::with_capacity(key.len() + value.len());
468                record.extend_from_slice(key);
469                record.extend_from_slice(value);
470                self.trigger_crash(CrashOperation::SstWrite, CrashTiming::Before);
471                if let Some(hooks) = self.io_hooks() {
472                    hooks.before_sst_write(&record).map_err(Error::Io)?;
473                }
474                self.trigger_crash(CrashOperation::SstWrite, CrashTiming::During);
475            }
476
477            writer.append(key, value)?;
478
479            #[cfg(feature = "test-hooks")]
480            self.trigger_crash(CrashOperation::SstWrite, CrashTiming::After);
481        }
482        drop(data);
483
484        #[cfg(feature = "test-hooks")]
485        self.trigger_crash(CrashOperation::SstFinalize, CrashTiming::Before);
486        let _footer = writer.finish()?;
487        #[cfg(feature = "test-hooks")]
488        self.trigger_crash(CrashOperation::SstFinalize, CrashTiming::After);
489        let reader = SstableReader::open(path)?;
490        // Also emit a placeholder vector segment alongside SSTable for future vector recovery.
491        let vec_path = path.with_extension("vec");
492        write_empty_vector_segment(&vec_path)?;
493
494        let mut slot = self.state.sstable.write().unwrap();
495        *slot = Some(reader);
496
497        #[cfg(feature = "test-hooks")]
498        self.notify_compaction(CrashTiming::After);
499        Ok(())
500    }
501
502    /// Replays the WAL to restore the state of the in-memory map.
503    fn replay(&self) -> Result<()> {
504        let path = match &self.state.wal_path {
505            Some(p) => p,
506            None => return Ok(()),
507        };
508        if !path.exists() || std::fs::metadata(path)?.len() == 0 {
509            return Ok(());
510        }
511
512        let mut data = self.state.data.write().unwrap();
513        let mut max_txn_id = 0;
514        let mut max_version = self.state.commit_version.load(Ordering::Acquire);
515        let reader = WalReader::new(path)?;
516        let mut pending_txns: HashMap<TxnId, Vec<(Key, Option<Value>)>> = HashMap::new();
517
518        for record_result in reader {
519            match record_result? {
520                WalRecord::Begin(txn_id) => {
521                    max_txn_id = max_txn_id.max(txn_id.0);
522                    pending_txns.entry(txn_id).or_default();
523                }
524                WalRecord::Put(txn_id, key, value) => {
525                    max_txn_id = max_txn_id.max(txn_id.0);
526                    pending_txns
527                        .entry(txn_id)
528                        .or_default()
529                        .push((key, Some(value)));
530                }
531                WalRecord::Delete(txn_id, key) => {
532                    max_txn_id = max_txn_id.max(txn_id.0);
533                    pending_txns.entry(txn_id).or_default().push((key, None));
534                }
535                WalRecord::Commit(txn_id) => {
536                    if let Some(writes) = pending_txns.remove(&txn_id) {
537                        max_version += 1;
538                        for (key, value) in writes {
539                            if let Some(v) = value {
540                                data.insert(key, (v, max_version));
541                            } else {
542                                data.remove(&key);
543                            }
544                        }
545                    }
546                }
547            }
548        }
549
550        self.state
551            .next_txn_id
552            .store(max_txn_id + 1, Ordering::SeqCst);
553        self.state
554            .commit_version
555            .store(max_version, Ordering::SeqCst);
556        Ok(())
557    }
558
559    fn load_sstable(&self) -> Result<()> {
560        let path = match &self.state.sstable_path {
561            Some(p) => p,
562            None => return Ok(()),
563        };
564        if !path.exists() {
565            return Ok(());
566        }
567
568        let mut reader = match SstableReader::open(path) {
569            Ok(reader) => reader,
570            // Crash-recovery tolerance: a truncated or corrupt SSTable means the
571            // durable segment was torn by a crash and its contents are
572            // unrecoverable. Discard the unreadable segment and continue recovery
573            // from the WAL — this mirrors `WalReader`'s torn-tail handling. Only
574            // corruption signatures are tolerated; genuine I/O faults still
575            // propagate so real failures are not masked.
576            Err(e @ (Error::InvalidFormat(_) | Error::ChecksumMismatch)) => {
577                warn!(
578                    path = %path.display(),
579                    error = %e,
580                    "discarding unreadable SSTable during recovery; replaying WAL only"
581                );
582                return Ok(());
583            }
584            Err(Error::Io(io)) if io.kind() == std::io::ErrorKind::UnexpectedEof => {
585                warn!(
586                    path = %path.display(),
587                    "discarding truncated SSTable during recovery; replaying WAL only"
588                );
589                return Ok(());
590            }
591            Err(e) => return Err(e),
592        };
593        let mut data = self.state.data.write().unwrap();
594        let mut version = self.state.commit_version.load(Ordering::Acquire);
595
596        let keys: Vec<Key> = reader
597            .index()
598            .iter()
599            .map(|entry| entry.key.clone())
600            .collect();
601
602        for key in keys {
603            if let Some(value) = reader.get(&key)? {
604                version += 1;
605                data.insert(key, (value, version));
606            }
607        }
608
609        self.state.commit_version.store(version, Ordering::SeqCst);
610        let mut slot = self.state.sstable.write().unwrap();
611        *slot = Some(reader);
612        Ok(())
613    }
614
615    /// Loads SSTable then replays WAL to restore state.
616    fn recover(&self) -> Result<()> {
617        self.load_sstable()?;
618        self.replay()?;
619        self.state.recompute_current_memory();
620        Ok(())
621    }
622
623    fn sstable_get(&self, key: &Key) -> Result<Option<Value>> {
624        let mut guard = self.state.sstable.write().unwrap();
625        if let Some(reader) = guard.as_mut() {
626            return reader.get(key);
627        }
628        Ok(None)
629    }
630
631    fn begin_internal(&self, mode: TxnMode) -> Result<MemoryTransaction<'_>> {
632        let txn_id = self.state.next_txn_id.fetch_add(1, Ordering::SeqCst);
633        let start_version = self.state.commit_version.load(Ordering::Acquire);
634        Ok(MemoryTransaction::new(
635            self,
636            TxnId(txn_id),
637            mode,
638            start_version,
639        ))
640    }
641}
642
643impl<'a> TxnManager<'a, MemoryTransaction<'a>> for &'a MemoryTxnManager {
644    fn begin(&'a self, mode: TxnMode) -> Result<MemoryTransaction<'a>> {
645        self.begin_internal(mode)
646    }
647
648    fn commit(&'a self, mut txn: MemoryTransaction<'a>) -> Result<()> {
649        if txn.state != TxnState::Active {
650            return Err(Error::TxnClosed);
651        }
652        if txn.mode == TxnMode::ReadOnly || txn.writes.is_empty() {
653            txn.state = TxnState::Committed;
654            return Ok(());
655        }
656
657        let mut data = self.state.data.write().unwrap();
658
659        for key in txn.read_set.keys() {
660            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
661            if current_version > txn.start_version {
662                return Err(Error::TxnConflict);
663            }
664        }
665
666        // Detect write-write conflicts even when the key was never read.
667        for key in txn.writes.keys() {
668            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
669            if current_version > txn.start_version {
670                return Err(Error::TxnConflict);
671            }
672        }
673
674        // Compute prospective memory usage and enforce limits before mutating state.
675        let mut delta: isize = 0;
676        for (key, value) in &txn.writes {
677            let current_size = data.get(key).map(|(v, _)| key.len() + v.len()).unwrap_or(0);
678            let new_size = match value {
679                Some(v) => key.len() + v.len(),
680                None => 0,
681            };
682            delta += new_size as isize - current_size as isize;
683        }
684
685        let current_mem = self.state.current_memory.load(Ordering::Relaxed);
686        let prospective = if delta >= 0 {
687            current_mem.saturating_add(delta as usize)
688        } else {
689            current_mem.saturating_sub(delta.unsigned_abs())
690        };
691
692        if delta > 0 {
693            self.state.check_memory_limit(delta as usize)?;
694        }
695
696        let commit_version = self.state.commit_version.fetch_add(1, Ordering::AcqRel) + 1;
697
698        self.write_wal(txn.id, &txn.writes)?;
699
700        for (key, value) in std::mem::take(&mut txn.writes) {
701            if let Some(v) = value {
702                data.insert(key, (v, commit_version));
703            } else {
704                data.remove(&key);
705            }
706        }
707
708        self.state
709            .current_memory
710            .store(prospective, Ordering::Relaxed);
711
712        txn.state = TxnState::Committed;
713        Ok(())
714    }
715
716    fn rollback(&'a self, mut txn: MemoryTransaction<'a>) -> Result<()> {
717        if txn.state != TxnState::Active {
718            return Err(Error::TxnClosed);
719        }
720        txn.state = TxnState::RolledBack;
721        Ok(())
722    }
723}
724
725/// An in-memory transaction that enforces snapshot isolation.
726pub struct MemoryTransaction<'a> {
727    manager: &'a MemoryTxnManager,
728    id: TxnId,
729    mode: TxnMode,
730    state: TxnState,
731    start_version: u64,
732    writes: BTreeMap<Key, Option<Value>>,
733    read_set: HashMap<Key, u64>,
734}
735
736impl<'a> MemoryTransaction<'a> {
737    fn new(manager: &'a MemoryTxnManager, id: TxnId, mode: TxnMode, start_version: u64) -> Self {
738        Self {
739            manager,
740            id,
741            mode,
742            state: TxnState::Active,
743            start_version,
744            writes: BTreeMap::new(),
745            read_set: HashMap::new(),
746        }
747    }
748
749    fn ensure_active(&self) -> Result<()> {
750        if self.state != TxnState::Active {
751            return Err(Error::TxnClosed);
752        }
753        Ok(())
754    }
755
756    /// トランザクションを消費せずにロールバックする。
757    pub(crate) fn rollback_in_place(&mut self) -> Result<()> {
758        if self.state != TxnState::Active {
759            return Err(Error::TxnClosed);
760        }
761        self.state = TxnState::RolledBack;
762        Ok(())
763    }
764
765    fn scan_range_internal(&mut self, start: &[u8], end: &[u8]) -> MergedScanIter<'_> {
766        let start_vec = start.to_vec();
767        let end_vec = end.to_vec();
768        let data_guard = self.manager.state.data.read().unwrap();
769        let data_ptr: *const BTreeMap<Key, VersionedValue> = &*data_guard;
770        let data_iter = unsafe {
771            // Safety: data_guard keeps the map alive for the lifetime of the iterator.
772            (&*data_ptr).range((Included(start_vec.clone()), Excluded(end_vec.clone())))
773        };
774        let write_iter = self
775            .writes
776            .range((Included(start_vec.clone()), Excluded(end_vec.clone())));
777
778        MergedScanIter::new(
779            data_guard,
780            data_iter,
781            write_iter,
782            None,
783            Some(end_vec),
784            self.start_version,
785            &mut self.read_set,
786        )
787    }
788
789    fn scan_prefix_internal(&mut self, prefix: &[u8]) -> MergedScanIter<'_> {
790        let prefix_vec = prefix.to_vec();
791        let data_guard = self.manager.state.data.read().unwrap();
792        let data_ptr: *const BTreeMap<Key, VersionedValue> = &*data_guard;
793        let data_iter = unsafe {
794            // Safety: data_guard keeps the map alive for the lifetime of the iterator.
795            (&*data_ptr).range(prefix_vec.clone()..)
796        };
797        let write_iter = self.writes.range(prefix_vec.clone()..);
798        MergedScanIter::new(
799            data_guard,
800            data_iter,
801            write_iter,
802            Some(prefix_vec),
803            None,
804            self.start_version,
805            &mut self.read_set,
806        )
807    }
808}
809
810impl<'a> KVTransaction<'a> for MemoryTransaction<'a> {
811    fn id(&self) -> TxnId {
812        self.id
813    }
814
815    fn mode(&self) -> TxnMode {
816        self.mode
817    }
818
819    fn get(&mut self, key: &Key) -> Result<Option<Value>> {
820        if self.state != TxnState::Active {
821            return Err(Error::TxnClosed);
822        }
823
824        if let Some(value) = self.writes.get(key) {
825            return Ok(value.clone());
826        }
827
828        let result = {
829            let data = self.manager.state.data.read().unwrap();
830            data.get(key).cloned()
831        };
832
833        if let Some((v, version)) = result {
834            self.read_set.insert(key.clone(), version);
835            return Ok(Some(v));
836        }
837
838        // Read-through to SSTable if not found in memory.
839        if let Some(value) = self.manager.sstable_get(key)? {
840            let version = self.manager.state.commit_version.load(Ordering::Acquire);
841            self.read_set.insert(key.clone(), version);
842            return Ok(Some(value));
843        }
844
845        Ok(None)
846    }
847
848    fn put(&mut self, key: Key, value: Value) -> Result<()> {
849        if self.state != TxnState::Active {
850            return Err(Error::TxnClosed);
851        }
852        if self.mode == TxnMode::ReadOnly {
853            return Err(Error::TxnReadOnly);
854        }
855        self.writes.insert(key, Some(value));
856        Ok(())
857    }
858
859    fn delete(&mut self, key: Key) -> Result<()> {
860        if self.state != TxnState::Active {
861            return Err(Error::TxnClosed);
862        }
863        if self.mode == TxnMode::ReadOnly {
864            return Err(Error::TxnReadOnly);
865        }
866        self.writes.insert(key, None);
867        Ok(())
868    }
869
870    fn scan_prefix(
871        &mut self,
872        prefix: &[u8],
873    ) -> Result<Box<dyn Iterator<Item = (Key, Value)> + '_>> {
874        self.ensure_active()?;
875        let iter = self
876            .scan_prefix_internal(prefix)
877            .filter_map(|(k, v)| v.map(|val| (k, val)));
878        Ok(Box::new(iter))
879    }
880
881    fn scan_range(
882        &mut self,
883        start: &[u8],
884        end: &[u8],
885    ) -> Result<Box<dyn Iterator<Item = (Key, Value)> + '_>> {
886        self.ensure_active()?;
887        let iter = self
888            .scan_range_internal(start, end)
889            .filter_map(|(k, v)| v.map(|val| (k, val)));
890        Ok(Box::new(iter))
891    }
892
893    fn commit_self(mut self) -> Result<()> {
894        if self.state != TxnState::Active {
895            return Err(Error::TxnClosed);
896        }
897        if self.mode == TxnMode::ReadOnly || self.writes.is_empty() {
898            self.state = TxnState::Committed;
899            return Ok(());
900        }
901
902        let mut data = self.manager.state.data.write().unwrap();
903
904        // Check read-set for conflicts
905        for key in self.read_set.keys() {
906            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
907            if current_version > self.start_version {
908                return Err(Error::TxnConflict);
909            }
910        }
911
912        // Check write-write conflicts
913        for key in self.writes.keys() {
914            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
915            if current_version > self.start_version {
916                return Err(Error::TxnConflict);
917            }
918        }
919
920        // Compute prospective memory usage
921        let mut delta: isize = 0;
922        for (key, value) in &self.writes {
923            let current_size = data.get(key).map(|(v, _)| key.len() + v.len()).unwrap_or(0);
924            let new_size = match value {
925                Some(v) => key.len() + v.len(),
926                None => 0,
927            };
928            delta += new_size as isize - current_size as isize;
929        }
930
931        let current_mem = self.manager.state.current_memory.load(Ordering::Relaxed);
932        let prospective = if delta >= 0 {
933            current_mem.saturating_add(delta as usize)
934        } else {
935            current_mem.saturating_sub(delta.unsigned_abs())
936        };
937
938        if delta > 0 {
939            self.manager.state.check_memory_limit(delta as usize)?;
940        }
941
942        let commit_version = self
943            .manager
944            .state
945            .commit_version
946            .fetch_add(1, Ordering::AcqRel)
947            + 1;
948
949        // WAL write
950        self.manager.write_wal(self.id, &self.writes)?;
951
952        // Apply writes
953        for (key, value) in std::mem::take(&mut self.writes) {
954            if let Some(v) = value {
955                data.insert(key, (v, commit_version));
956            } else {
957                data.remove(&key);
958            }
959        }
960
961        self.manager
962            .state
963            .current_memory
964            .store(prospective, Ordering::Relaxed);
965
966        self.state = TxnState::Committed;
967        Ok(())
968    }
969
970    fn rollback_self(mut self) -> Result<()> {
971        if self.state != TxnState::Active {
972            return Err(Error::TxnClosed);
973        }
974        self.state = TxnState::RolledBack;
975        Ok(())
976    }
977}
978
979/// Lazy merge iterator that overlays in-flight writes onto a snapshot guard.
980struct MergedScanIter<'a> {
981    _data_guard: RwLockReadGuard<'a, BTreeMap<Key, VersionedValue>>,
982    data_iter: std::collections::btree_map::Range<'a, Key, VersionedValue>,
983    write_iter: std::collections::btree_map::Range<'a, Key, Option<Value>>,
984    data_peek: Option<(Key, (Value, u64))>,
985    write_peek: Option<(Key, Option<Value>)>,
986    prefix: Option<Vec<u8>>,
987    end: Option<Key>,
988    start_version: u64,
989    read_set: &'a mut HashMap<Key, u64>,
990}
991
992impl<'a> MergedScanIter<'a> {
993    #[allow(clippy::too_many_arguments)]
994    fn new(
995        data_guard: std::sync::RwLockReadGuard<'a, BTreeMap<Key, VersionedValue>>,
996        data_iter: std::collections::btree_map::Range<'a, Key, VersionedValue>,
997        write_iter: std::collections::btree_map::Range<'a, Key, Option<Value>>,
998        prefix: Option<Vec<u8>>,
999        end: Option<Key>,
1000        start_version: u64,
1001        read_set: &'a mut HashMap<Key, u64>,
1002    ) -> Self {
1003        let mut iter = Self {
1004            _data_guard: data_guard,
1005            data_iter,
1006            write_iter,
1007            data_peek: None,
1008            write_peek: None,
1009            prefix,
1010            end,
1011            start_version,
1012            read_set,
1013        };
1014        iter.advance_data();
1015        iter.advance_write();
1016        iter
1017    }
1018
1019    fn advance_data(&mut self) {
1020        self.data_peek = None;
1021        while let Some((k, (v, ver))) = self.data_iter.next().map(|(k, v)| (k.clone(), v.clone())) {
1022            if let Some(end) = &self.end {
1023                if k >= *end {
1024                    return;
1025                }
1026            }
1027            if let Some(prefix) = &self.prefix {
1028                if !k.starts_with(prefix) {
1029                    return;
1030                }
1031            }
1032            if ver > self.start_version {
1033                continue;
1034            }
1035            self.data_peek = Some((k, (v, ver)));
1036            return;
1037        }
1038    }
1039
1040    fn advance_write(&mut self) {
1041        self.write_peek = None;
1042        if let Some((k, v)) = self.write_iter.next().map(|(k, v)| (k.clone(), v.clone())) {
1043            if let Some(end) = &self.end {
1044                if k >= *end {
1045                    return;
1046                }
1047            }
1048            if let Some(prefix) = &self.prefix {
1049                if !k.starts_with(prefix) {
1050                    return;
1051                }
1052            }
1053            self.write_peek = Some((k, v));
1054        }
1055    }
1056}
1057
1058impl<'a> Iterator for MergedScanIter<'a> {
1059    type Item = (Key, Option<Value>);
1060
1061    fn next(&mut self) -> Option<Self::Item> {
1062        let data_key = self.data_peek.as_ref().map(|(k, _)| k.clone());
1063        let write_key = self.write_peek.as_ref().map(|(k, _)| k.clone());
1064
1065        match (data_key, write_key) {
1066            (Some(dk), Some(wk)) => {
1067                if dk == wk {
1068                    let (_, (_, ver)) = self.data_peek.take().unwrap();
1069                    let (_, write_val) = self.write_peek.take().unwrap();
1070                    self.read_set.insert(dk.clone(), ver);
1071                    self.advance_data();
1072                    self.advance_write();
1073                    Some((dk, write_val))
1074                } else if dk < wk {
1075                    let (k, (v, ver)) = self.data_peek.take().unwrap();
1076                    self.read_set.insert(k.clone(), ver);
1077                    self.advance_data();
1078                    Some((k, Some(v)))
1079                } else {
1080                    let (k, write_val) = self.write_peek.take().unwrap();
1081                    self.advance_write();
1082                    Some((k, write_val))
1083                }
1084            }
1085            (Some(_), None) => {
1086                let (k, (v, ver)) = self.data_peek.take().unwrap();
1087                self.read_set.insert(k.clone(), ver);
1088                self.advance_data();
1089                Some((k, Some(v)))
1090            }
1091            (None, Some(_)) => {
1092                let (k, write_val) = self.write_peek.take().unwrap();
1093                self.advance_write();
1094                Some((k, write_val))
1095            }
1096            (None, None) => None,
1097        }
1098    }
1099}
1100
1101impl<'a> Drop for MemoryTransaction<'a> {
1102    fn drop(&mut self) {
1103        if self.state == TxnState::Active {
1104            self.state = TxnState::RolledBack;
1105        }
1106    }
1107}
1108
1109#[cfg(all(test, not(target_arch = "wasm32")))]
1110mod tests {
1111    use super::*;
1112    use crate::{KVTransaction, TxnManager};
1113    use tempfile::tempdir;
1114    use tracing::Level;
1115
1116    fn key(s: &str) -> Key {
1117        s.as_bytes().to_vec()
1118    }
1119
1120    fn value(s: &str) -> Value {
1121        s.as_bytes().to_vec()
1122    }
1123
1124    fn committed_value_after_reopen(wal_path: &Path, key: Key) -> Option<Value> {
1125        let reopened = MemoryKV::open(wal_path).unwrap();
1126        let manager = reopened.txn_manager();
1127        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1128        txn.get(&key).unwrap()
1129    }
1130
1131    fn write_flush_and_corrupt_sstable<F>(corrupt: F) -> (tempfile::TempDir, PathBuf)
1132    where
1133        F: FnOnce(&Path),
1134    {
1135        let dir = tempdir().unwrap();
1136        let wal_path = dir.path().join("wal.log");
1137        {
1138            let store = MemoryKV::open(&wal_path).unwrap();
1139            let manager = store.txn_manager();
1140            let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1141            txn.put(key("k1"), value("v1")).unwrap();
1142            manager.commit(txn).unwrap();
1143            store.flush().unwrap();
1144        }
1145
1146        corrupt(&wal_path.with_extension("sst"));
1147        (dir, wal_path)
1148    }
1149
1150    #[cfg(feature = "test-hooks")]
1151    struct FailsBeforeFsync;
1152
1153    #[cfg(feature = "test-hooks")]
1154    impl IoHooks for FailsBeforeFsync {
1155        fn before_fsync(&self) -> std::io::Result<()> {
1156            Err(std::io::Error::other("injected WAL fsync failure"))
1157        }
1158    }
1159
1160    #[cfg(feature = "test-hooks")]
1161    #[test]
1162    fn commit_self_wal_fsync_failure_does_not_ack_or_apply() {
1163        let dir = tempdir().unwrap();
1164        let wal_path = dir.path().join("wal.log");
1165        let store = MemoryKV::open_with_io_hooks(&wal_path, Arc::new(FailsBeforeFsync)).unwrap();
1166        let manager = store.txn_manager();
1167
1168        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1169        txn.put(key("not-acked"), value("value")).unwrap();
1170        let result = txn.commit_self();
1171        assert!(matches!(result, Err(Error::Io(_))));
1172
1173        let mut read_txn = manager.begin(TxnMode::ReadOnly).unwrap();
1174        assert_eq!(read_txn.get(&key("not-acked")).unwrap(), None);
1175    }
1176
1177    #[test]
1178    fn test_put_and_get_transient() {
1179        let store = MemoryKV::new();
1180        let manager = store.txn_manager();
1181        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1182        txn.put(key("hello"), value("world")).unwrap();
1183        let val = txn.get(&key("hello")).unwrap();
1184        assert_eq!(val, Some(value("world")));
1185        manager.commit(txn).unwrap();
1186
1187        let mut txn2 = manager.begin(TxnMode::ReadOnly).unwrap();
1188        let val2 = txn2.get(&key("hello")).unwrap();
1189        assert_eq!(val2, Some(value("world")));
1190    }
1191
1192    #[test]
1193    fn test_occ_conflict() {
1194        let store = MemoryKV::new();
1195        let manager = store.txn_manager();
1196
1197        let mut t1 = manager.begin(TxnMode::ReadWrite).unwrap();
1198        t1.get(&key("k1")).unwrap();
1199
1200        let mut t2 = manager.begin(TxnMode::ReadWrite).unwrap();
1201        t2.put(key("k1"), value("v2")).unwrap();
1202        assert!(manager.commit(t2).is_ok());
1203
1204        t1.put(key("k1"), value("v1")).unwrap();
1205        let result = manager.commit(t1);
1206        assert!(matches!(result, Err(Error::TxnConflict)));
1207    }
1208
1209    #[test]
1210    fn test_blind_write_conflict() {
1211        let store = MemoryKV::new();
1212        let manager = store.txn_manager();
1213
1214        let mut t1 = manager.begin(TxnMode::ReadWrite).unwrap();
1215        t1.put(key("k1"), value("v1")).unwrap();
1216
1217        let mut t2 = manager.begin(TxnMode::ReadWrite).unwrap();
1218        t2.put(key("k1"), value("v2")).unwrap();
1219        assert!(manager.commit(t2).is_ok());
1220
1221        let result = manager.commit(t1);
1222        assert!(matches!(result, Err(Error::TxnConflict)));
1223    }
1224
1225    #[test]
1226    fn test_read_only_write_fails() {
1227        let store = MemoryKV::new();
1228        let manager = store.txn_manager();
1229        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1230        assert!(matches!(
1231            txn.put(key("k1"), value("v1")),
1232            Err(Error::TxnReadOnly)
1233        ));
1234        assert!(matches!(txn.delete(key("k1")), Err(Error::TxnReadOnly)));
1235    }
1236
1237    #[test]
1238    fn test_txn_closed_error() {
1239        let store = MemoryKV::new();
1240        let manager = store.txn_manager();
1241        let txn = manager.begin(TxnMode::ReadWrite).unwrap();
1242        manager.commit(txn).unwrap();
1243
1244        // This is tricky to test because commit takes ownership.
1245        // We can test by creating a new txn and manually setting its state.
1246        let mut closed_txn = manager.begin(TxnMode::ReadWrite).unwrap();
1247        closed_txn.state = TxnState::Committed;
1248        assert!(matches!(closed_txn.get(&key("k1")), Err(Error::TxnClosed)));
1249        assert!(matches!(
1250            closed_txn.put(key("k1"), value("v1")),
1251            Err(Error::TxnClosed)
1252        ));
1253    }
1254
1255    #[test]
1256    fn test_get_not_found() {
1257        let store = MemoryKV::new();
1258        let manager = store.txn_manager();
1259        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1260        let res = txn.get(&key("non-existent"));
1261        assert!(res.is_ok());
1262        assert!(res.unwrap().is_none());
1263    }
1264
1265    #[test]
1266    fn flush_and_reopen_reads_from_sstable() {
1267        let dir = tempdir().unwrap();
1268        let wal_path = dir.path().join("wal.log");
1269        {
1270            let store = MemoryKV::open(&wal_path).unwrap();
1271            let manager = store.txn_manager();
1272            let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1273            txn.put(key("k1"), value("v1")).unwrap();
1274            manager.commit(txn).unwrap();
1275            store.flush().unwrap();
1276        }
1277
1278        let reopened = MemoryKV::open(&wal_path).unwrap();
1279        let manager = reopened.txn_manager();
1280        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1281        assert_eq!(txn.get(&key("k1")).unwrap(), Some(value("v1")));
1282    }
1283
1284    #[test]
1285    fn corrupt_sstable_header_is_discarded_and_wal_recovers() {
1286        let (_dir, wal_path) = write_flush_and_corrupt_sstable(|sst_path| {
1287            let mut file = std::fs::OpenOptions::new()
1288                .write(true)
1289                .open(sst_path)
1290                .unwrap();
1291            use std::io::{Seek, Write};
1292            file.seek(std::io::SeekFrom::Start(0)).unwrap();
1293            file.write_all(b"BAD!").unwrap();
1294            file.sync_all().unwrap();
1295        });
1296        let err = SstableReader::open(&wal_path.with_extension("sst")).unwrap_err();
1297        assert!(matches!(err, Error::InvalidFormat(_)));
1298
1299        assert_eq!(
1300            committed_value_after_reopen(&wal_path, key("k1")),
1301            Some(value("v1"))
1302        );
1303    }
1304
1305    #[test]
1306    fn corrupt_sstable_payload_checksum_is_discarded_and_wal_recovers() {
1307        let (_dir, wal_path) = write_flush_and_corrupt_sstable(|sst_path| {
1308            let mut file = std::fs::OpenOptions::new()
1309                .read(true)
1310                .write(true)
1311                .open(sst_path)
1312                .unwrap();
1313            use std::io::{Read, Seek, Write};
1314            file.seek(std::io::SeekFrom::Start(16 + 8 + key("k1").len() as u64))
1315                .unwrap();
1316            let mut byte = [0u8; 1];
1317            file.read_exact(&mut byte).unwrap();
1318            file.seek(std::io::SeekFrom::Current(-1)).unwrap();
1319            file.write_all(&[byte[0] ^ 0xFF]).unwrap();
1320            file.sync_all().unwrap();
1321        });
1322        let err = SstableReader::open(&wal_path.with_extension("sst")).unwrap_err();
1323        assert!(matches!(err, Error::ChecksumMismatch));
1324
1325        assert_eq!(
1326            committed_value_after_reopen(&wal_path, key("k1")),
1327            Some(value("v1"))
1328        );
1329    }
1330
1331    #[test]
1332    fn truncated_sstable_is_discarded_and_wal_recovers() {
1333        let (_dir, wal_path) = write_flush_and_corrupt_sstable(|sst_path| {
1334            let file = std::fs::OpenOptions::new()
1335                .write(true)
1336                .open(sst_path)
1337                .unwrap();
1338            file.set_len(16).unwrap();
1339            file.sync_all().unwrap();
1340        });
1341        let err = SstableReader::open(&wal_path.with_extension("sst")).unwrap_err();
1342        assert!(matches!(err, Error::InvalidFormat(_)));
1343
1344        assert_eq!(
1345            committed_value_after_reopen(&wal_path, key("k1")),
1346            Some(value("v1"))
1347        );
1348    }
1349
1350    #[test]
1351    fn wal_recovers_committed_tombstone_on_reopen() {
1352        let dir = tempdir().unwrap();
1353        let wal_path = dir.path().join("wal.log");
1354        {
1355            let store = MemoryKV::open(&wal_path).unwrap();
1356            let manager = store.txn_manager();
1357            let mut put_txn = manager.begin(TxnMode::ReadWrite).unwrap();
1358            put_txn.put(key("deleted"), value("value")).unwrap();
1359            manager.commit(put_txn).unwrap();
1360
1361            let mut delete_txn = manager.begin(TxnMode::ReadWrite).unwrap();
1362            delete_txn.delete(key("deleted")).unwrap();
1363            manager.commit(delete_txn).unwrap();
1364        }
1365
1366        assert_eq!(
1367            committed_value_after_reopen(&wal_path, key("deleted")),
1368            None
1369        );
1370    }
1371
1372    #[test]
1373    fn wal_overlays_sstable_on_reopen() {
1374        let dir = tempdir().unwrap();
1375        let wal_path = dir.path().join("wal.log");
1376        {
1377            let store = MemoryKV::open(&wal_path).unwrap();
1378            let manager = store.txn_manager();
1379            let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1380            txn.put(key("k1"), value("v1")).unwrap();
1381            manager.commit(txn).unwrap();
1382            store.flush().unwrap();
1383
1384            let mut txn2 = manager.begin(TxnMode::ReadWrite).unwrap();
1385            txn2.put(key("k1"), value("v2")).unwrap();
1386            manager.commit(txn2).unwrap();
1387        }
1388
1389        let reopened = MemoryKV::open(&wal_path).unwrap();
1390        let manager = reopened.txn_manager();
1391        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1392        assert_eq!(txn.get(&key("k1")).unwrap(), Some(value("v2")));
1393    }
1394
1395    #[test]
1396    fn scan_prefix_merges_snapshot_and_writes() {
1397        let store = MemoryKV::new();
1398        let manager = store.txn_manager();
1399
1400        let mut seed = manager.begin(TxnMode::ReadWrite).unwrap();
1401        seed.put(key("p:1"), value("old1")).unwrap();
1402        seed.put(key("p:2"), value("old2")).unwrap();
1403        seed.put(key("q:1"), value("other")).unwrap();
1404        manager.commit(seed).unwrap();
1405
1406        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1407        txn.put(key("p:1"), value("new1")).unwrap();
1408        txn.delete(key("p:2")).unwrap();
1409        txn.put(key("p:3"), value("new3")).unwrap();
1410
1411        let results: Vec<_> = txn.scan_prefix(b"p:").unwrap().collect();
1412        assert_eq!(
1413            results,
1414            vec![(key("p:1"), value("new1")), (key("p:3"), value("new3"))]
1415        );
1416    }
1417
1418    #[test]
1419    fn scan_range_skips_newer_versions() {
1420        let store = MemoryKV::new();
1421        let manager = store.txn_manager();
1422
1423        let mut seed = manager.begin(TxnMode::ReadWrite).unwrap();
1424        seed.put(key("b"), value("v1")).unwrap();
1425        manager.commit(seed).unwrap();
1426
1427        let mut txn1 = manager.begin(TxnMode::ReadWrite).unwrap();
1428
1429        let mut txn2 = manager.begin(TxnMode::ReadWrite).unwrap();
1430        txn2.put(key("ba"), value("v2")).unwrap();
1431        manager.commit(txn2).unwrap();
1432
1433        let results: Vec<_> = txn1.scan_range(b"b", b"c").unwrap().collect();
1434        assert_eq!(results, vec![(key("b"), value("v1"))]);
1435    }
1436
1437    #[test]
1438    fn scan_range_records_reads_for_conflict_detection() {
1439        let store = MemoryKV::new();
1440        let manager = store.txn_manager();
1441
1442        let mut seed = manager.begin(TxnMode::ReadWrite).unwrap();
1443        seed.put(key("k1"), value("v1")).unwrap();
1444        manager.commit(seed).unwrap();
1445
1446        let mut t1 = manager.begin(TxnMode::ReadWrite).unwrap();
1447        let results: Vec<_> = t1.scan_range(b"k0", b"kz").unwrap().collect();
1448        assert_eq!(results, vec![(key("k1"), value("v1"))]);
1449        t1.put(key("k_new"), value("v_new")).unwrap();
1450
1451        let mut t2 = manager.begin(TxnMode::ReadWrite).unwrap();
1452        t2.put(key("k1"), value("v2")).unwrap();
1453        manager.commit(t2).unwrap();
1454
1455        let result = manager.commit(t1);
1456        assert!(matches!(result, Err(Error::TxnConflict)));
1457    }
1458
1459    #[test]
1460    fn memory_stats_tracks_put_and_delete() {
1461        let store = MemoryKV::new();
1462        let manager = store.txn_manager();
1463
1464        let stats = manager.memory_stats();
1465        assert_eq!(stats.total_bytes, 0);
1466        assert_eq!(stats.kv_bytes, 0);
1467        assert_eq!(stats.index_bytes, 0);
1468
1469        // Insert a value and commit.
1470        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1471        txn.put(key("a"), value("1234")).unwrap(); // key=1, value=4 => 5 bytes
1472        manager.commit(txn).unwrap();
1473
1474        let stats = manager.memory_stats();
1475        assert_eq!(stats.total_bytes, 5);
1476        assert_eq!(stats.kv_bytes, 5);
1477        assert_eq!(stats.index_bytes, 0);
1478
1479        // Delete and ensure usage returns to zero.
1480        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1481        txn.delete(key("a")).unwrap();
1482        manager.commit(txn).unwrap();
1483
1484        let stats = manager.memory_stats();
1485        assert_eq!(stats.total_bytes, 0);
1486        assert_eq!(stats.kv_bytes, 0);
1487    }
1488
1489    #[test]
1490    fn memory_limit_error_does_not_break_reads() {
1491        let store = MemoryKV::new_with_limit(Some(10));
1492        let manager = store.txn_manager();
1493
1494        // First insert within limit: key(2) + value(4) = 6.
1495        let mut txn = manager.begin_internal(TxnMode::ReadWrite).unwrap();
1496        txn.put(key("k1"), value("vvvv")).unwrap();
1497        manager.commit(txn).unwrap();
1498
1499        // Next insert would exceed limit: key(2) + value(6) + existing(6) -> 14 > 10.
1500        let mut txn2 = manager.begin_internal(TxnMode::ReadWrite).unwrap();
1501        txn2.put(key("k2"), value("vvvvvv")).unwrap();
1502        let result = manager.commit(txn2);
1503        assert!(matches!(result, Err(Error::MemoryLimitExceeded { .. })));
1504
1505        // Read still works and existing data intact.
1506        let mut read_txn = manager.begin_internal(TxnMode::ReadOnly).unwrap();
1507        let got = read_txn.get(&key("k1")).unwrap();
1508        assert_eq!(got, Some(value("vvvv")));
1509
1510        // Memory usage stays at the previous successful commit.
1511        let stats = manager.memory_stats();
1512        assert_eq!(stats.total_bytes, 6);
1513    }
1514
1515    struct VecWriter(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
1516
1517    impl std::io::Write for VecWriter {
1518        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1519            let mut guard = self.0.lock().unwrap();
1520            guard.extend_from_slice(buf);
1521            Ok(buf.len())
1522        }
1523
1524        fn flush(&mut self) -> std::io::Result<()> {
1525            Ok(())
1526        }
1527    }
1528
1529    #[test]
1530    fn compaction_skips_when_over_limit_and_logs_warning() {
1531        let store = MemoryKV::new_with_limit(Some(12));
1532        let manager = store.txn_manager();
1533
1534        // Populate data to track current memory: key(2)+val(6)=8 bytes.
1535        let mut txn = manager.begin_internal(TxnMode::ReadWrite).unwrap();
1536        txn.put(key("k1"), value("123456")).unwrap();
1537        manager.commit(txn).unwrap();
1538
1539        // Prepare log capture.
1540        let buffer = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
1541        let make_writer = {
1542            let buf = buffer.clone();
1543            move || VecWriter(buf.clone())
1544        };
1545        let subscriber = tracing_subscriber::fmt()
1546            .with_max_level(Level::WARN)
1547            .with_writer(make_writer)
1548            .without_time()
1549            .finish();
1550        let _guard = tracing::subscriber::set_default(subscriber);
1551
1552        // input=2 (assume one entry), output=10 => projected 8-2+10=16 > 12 -> skip.
1553        let ran = manager.compact_with_limit(2, 10, || Ok(())).unwrap();
1554        assert!(!ran);
1555
1556        // Memory usage unchanged.
1557        assert_eq!(manager.memory_stats().total_bytes, 8);
1558
1559        // Verify warning was logged.
1560        let log = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
1561        assert!(
1562            log.contains("compaction skipped due to memory limit"),
1563            "expected warning log, got: {}",
1564            log
1565        );
1566    }
1567}