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