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, OwnedKVScan, OwnedKVStore, OwnedKVTransaction};
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, Unbounded};
15use std::path::{Path, PathBuf};
16use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
17use std::sync::{Arc, Condvar, Mutex, 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
134impl OwnedKVStore for MemoryKV {
135    fn begin_owned_kv_transaction(
136        self: Arc<Self>,
137        mode: TxnMode,
138    ) -> Result<Box<dyn OwnedKVTransaction>> {
139        Ok(Box::new(OwnedMemoryTransaction::new(
140            self.manager.clone(),
141            mode,
142        )))
143    }
144}
145
146// The internal value stored in the BTreeMap, containing the data and its version.
147type VersionedValue = (Value, u64);
148
149/// The underlying shared state for the in-memory store.
150struct MemorySharedState {
151    /// The main data store, mapping keys to versioned values.
152    data: RwLock<BTreeMap<Key, VersionedValue>>,
153    /// The next transaction ID to be allocated.
154    next_txn_id: AtomicU64,
155    /// The current commit version of the database. Incremented on every successful commit.
156    commit_version: AtomicU64,
157    /// The WAL writer. If None, the store is transient.
158    wal_writer: Option<RwLock<WalWriter>>,
159    /// Optional WAL path for replay on reopen.
160    wal_path: Option<PathBuf>,
161    /// Optional SSTable reader for read-through.
162    sstable: RwLock<Option<SstableReader>>,
163    /// Optional SSTable path for flush/reopen.
164    sstable_path: Option<PathBuf>,
165    /// Optional memory upper limit (bytes) for in-memory mode。
166    memory_limit: RwLock<Option<usize>>,
167    /// Current memory consumption (bytes) tracked across operations。
168    current_memory: AtomicUsize,
169    /// Safe snapshot gate for owned incremental cursors.  It prevents writers from replacing
170    /// the map while a cursor owns its stable read view, without self-referential lock guards.
171    owned_snapshot_gate: Arc<OwnedSnapshotGate>,
172    #[cfg(feature = "test-hooks")]
173    /// Optional I/O hooks for fault injection (test only).
174    io_hooks: RwLock<Option<Arc<dyn IoHooks>>>,
175    #[cfg(feature = "test-hooks")]
176    /// Optional crash simulator for test-only crash points.
177    crash_sim: RwLock<Option<Arc<CrashSimulator>>>,
178}
179
180#[derive(Default)]
181struct OwnedSnapshotGate {
182    state: Mutex<OwnedSnapshotGateState>,
183    changed: Condvar,
184}
185
186#[derive(Default)]
187struct OwnedSnapshotGateState {
188    readers: usize,
189    writer: bool,
190}
191
192impl OwnedSnapshotGate {
193    fn acquire_reader(self: &Arc<Self>) -> OwnedSnapshotReader {
194        let mut state = self
195            .state
196            .lock()
197            .expect("owned snapshot gate mutex poisoned");
198        while state.writer {
199            state = self
200                .changed
201                .wait(state)
202                .expect("owned snapshot gate mutex poisoned");
203        }
204        state.readers = state.readers.saturating_add(1);
205        OwnedSnapshotReader {
206            gate: self.clone(),
207            released: false,
208        }
209    }
210
211    fn acquire_writer(self: &Arc<Self>) -> OwnedSnapshotWriter {
212        let mut state = self
213            .state
214            .lock()
215            .expect("owned snapshot gate mutex poisoned");
216        while state.writer || state.readers != 0 {
217            state = self
218                .changed
219                .wait(state)
220                .expect("owned snapshot gate mutex poisoned");
221        }
222        state.writer = true;
223        OwnedSnapshotWriter {
224            gate: self.clone(),
225            released: false,
226        }
227    }
228}
229
230struct OwnedSnapshotReader {
231    gate: Arc<OwnedSnapshotGate>,
232    released: bool,
233}
234
235impl Drop for OwnedSnapshotReader {
236    fn drop(&mut self) {
237        if !self.released {
238            let mut state = self
239                .gate
240                .state
241                .lock()
242                .expect("owned snapshot gate mutex poisoned");
243            state.readers = state.readers.saturating_sub(1);
244            self.released = true;
245            self.gate.changed.notify_all();
246        }
247    }
248}
249
250struct OwnedSnapshotWriter {
251    gate: Arc<OwnedSnapshotGate>,
252    released: bool,
253}
254
255impl Drop for OwnedSnapshotWriter {
256    fn drop(&mut self) {
257        if !self.released {
258            let mut state = self
259                .gate
260                .state
261                .lock()
262                .expect("owned snapshot gate mutex poisoned");
263            state.writer = false;
264            self.released = true;
265            self.gate.changed.notify_all();
266        }
267    }
268}
269
270impl MemorySharedState {
271    /// Check whether adding `additional` bytes would exceed the memory limit.
272    fn check_memory_limit(&self, additional: usize) -> Result<()> {
273        if let Some(limit) = *self.memory_limit.read().unwrap() {
274            let current = self.current_memory.load(Ordering::Relaxed);
275            let requested = current.saturating_add(additional);
276            if requested > limit {
277                return Err(Error::MemoryLimitExceeded { limit, requested });
278            }
279        }
280        Ok(())
281    }
282
283    /// Return current memory usage statistics.
284    fn memory_stats(&self) -> MemoryStats {
285        let kv_bytes = self.current_memory.load(Ordering::Relaxed);
286        MemoryStats {
287            total_bytes: kv_bytes,
288            kv_bytes,
289            index_bytes: 0,
290        }
291    }
292
293    /// Recompute tracked memory usage from existing data (used after recovery).
294    fn recompute_current_memory(&self) {
295        let data = self.data.read().unwrap();
296        let mut total = 0usize;
297        for (k, (v, _)) in data.iter() {
298            total = total.saturating_add(k.len() + v.len());
299        }
300        self.current_memory.store(total, Ordering::Relaxed);
301    }
302}
303
304/// A transaction manager backed by an in-memory map and optional WAL.
305pub struct MemoryTxnManager {
306    state: Arc<MemorySharedState>,
307}
308
309impl MemoryTxnManager {
310    fn new_with_params(
311        wal_writer: Option<WalWriter>,
312        wal_path: Option<PathBuf>,
313        sstable_path: Option<PathBuf>,
314        memory_limit: Option<usize>,
315    ) -> Self {
316        Self {
317            state: Arc::new(MemorySharedState {
318                data: RwLock::new(BTreeMap::new()),
319                next_txn_id: AtomicU64::new(1),
320                commit_version: AtomicU64::new(0),
321                wal_writer: wal_writer.map(RwLock::new),
322                wal_path,
323                sstable: RwLock::new(None),
324                sstable_path,
325                memory_limit: RwLock::new(memory_limit),
326                current_memory: AtomicUsize::new(0),
327                owned_snapshot_gate: Arc::new(OwnedSnapshotGate::default()),
328                #[cfg(feature = "test-hooks")]
329                io_hooks: RwLock::new(None),
330                #[cfg(feature = "test-hooks")]
331                crash_sim: RwLock::new(None),
332            }),
333        }
334    }
335
336    fn new(
337        wal_writer: Option<WalWriter>,
338        wal_path: Option<PathBuf>,
339        sstable_path: Option<PathBuf>,
340    ) -> Self {
341        Self::new_with_params(wal_writer, wal_path, sstable_path, None)
342    }
343
344    /// Creates an in-memory manager with an optional memory limit.
345    pub fn new_with_limit(limit: Option<usize>) -> Self {
346        Self::new_with_params(None, None, None, limit)
347    }
348
349    #[cfg(feature = "test-hooks")]
350    fn set_io_hooks(&self, hooks: Option<Arc<dyn IoHooks>>) {
351        let mut guard = self.state.io_hooks.write().unwrap();
352        *guard = hooks;
353    }
354
355    #[cfg(feature = "test-hooks")]
356    fn set_crash_sim(&self, crash_sim: Option<Arc<CrashSimulator>>) {
357        let mut guard = self.state.crash_sim.write().unwrap();
358        *guard = crash_sim;
359    }
360
361    #[cfg(feature = "test-hooks")]
362    fn io_hooks(&self) -> Option<Arc<dyn IoHooks>> {
363        self.state.io_hooks.read().unwrap().clone()
364    }
365
366    #[cfg(feature = "test-hooks")]
367    fn crash_sim(&self) -> Option<Arc<CrashSimulator>> {
368        self.state.crash_sim.read().unwrap().clone()
369    }
370
371    /// Returns current memory usage statistics.
372    pub fn memory_stats(&self) -> MemoryStats {
373        self.state.memory_stats()
374    }
375
376    /// Update the configured memory limit at runtime.
377    pub fn set_memory_limit(&self, limit: Option<usize>) {
378        let mut guard = self.state.memory_limit.write().unwrap();
379        *guard = limit;
380    }
381
382    /// Returns a snapshot clone of all key/value pairs.
383    pub fn snapshot(&self) -> Vec<(Key, Value)> {
384        let data = self.state.data.read().unwrap();
385        data.iter()
386            .map(|(k, (v, _))| (k.clone(), v.clone()))
387            .collect()
388    }
389
390    /// Clears all data and resets memory accounting.
391    pub fn clear_all(&self) {
392        let _snapshot_writer = self.state.owned_snapshot_gate.acquire_writer();
393        let mut data = self.state.data.write().unwrap();
394        data.clear();
395        drop(data);
396        self.state.current_memory.store(0, Ordering::Relaxed);
397        self.state.commit_version.store(0, Ordering::Relaxed);
398    }
399
400    /// Runs compaction if it can fit within the configured memory limit.
401    /// Returns Ok(true) when compaction executed, Ok(false) when skipped.
402    pub fn compact_with_limit<F>(
403        &self,
404        input_bytes: usize,
405        output_bytes: usize,
406        run: F,
407    ) -> Result<bool>
408    where
409        F: FnOnce() -> Result<()>,
410    {
411        if let Some(limit) = *self.state.memory_limit.read().unwrap() {
412            let current = self.state.current_memory.load(Ordering::Relaxed);
413            // predicted usage after compaction: current - input + output (clamped at 0)
414            let prospective = current
415                .saturating_sub(input_bytes)
416                .saturating_add(output_bytes);
417            if prospective > limit {
418                warn!(
419                    limit,
420                    requested = prospective,
421                    "compaction skipped due to memory limit"
422                );
423                return Ok(false);
424            }
425        }
426
427        run()?;
428
429        // Update tracked memory to reflect compaction result.
430        let current = self.state.current_memory.load(Ordering::Relaxed);
431        let new_usage = current
432            .saturating_sub(input_bytes)
433            .saturating_add(output_bytes);
434        self.state
435            .current_memory
436            .store(new_usage, Ordering::Relaxed);
437        Ok(true)
438    }
439
440    #[cfg(feature = "test-hooks")]
441    fn trigger_crash(&self, operation: CrashOperation, timing: CrashTiming) {
442        if let Some(sim) = self.crash_sim() {
443            sim.check_crash(operation, timing);
444        }
445    }
446
447    #[cfg(feature = "test-hooks")]
448    fn notify_wal_hooks(&self, data: &[u8], timing: CrashTiming) -> Result<()> {
449        match timing {
450            CrashTiming::Before => {
451                self.trigger_crash(CrashOperation::WalWrite, CrashTiming::Before);
452                if let Some(hooks) = self.io_hooks() {
453                    hooks.before_wal_write(data).map_err(Error::Io)?;
454                    hooks.before_fsync().map_err(Error::Io)?;
455                }
456                self.trigger_crash(CrashOperation::WalFsync, CrashTiming::Before);
457            }
458            CrashTiming::During => {
459                self.trigger_crash(CrashOperation::WalWrite, CrashTiming::During);
460                self.trigger_crash(CrashOperation::WalFsync, CrashTiming::During);
461            }
462            CrashTiming::After => {
463                self.trigger_crash(CrashOperation::WalWrite, CrashTiming::After);
464                self.trigger_crash(CrashOperation::WalFsync, CrashTiming::After);
465                if let Some(hooks) = self.io_hooks() {
466                    hooks.after_wal_write(data).map_err(Error::Io)?;
467                    hooks.after_fsync().map_err(Error::Io)?;
468                }
469            }
470        }
471        Ok(())
472    }
473
474    #[cfg(feature = "test-hooks")]
475    fn notify_compaction(&self, timing: CrashTiming) {
476        self.trigger_crash(CrashOperation::Compaction, timing);
477        if let Some(hooks) = self.io_hooks() {
478            match timing {
479                CrashTiming::Before => hooks.on_compaction_start(),
480                CrashTiming::After => hooks.on_compaction_end(),
481                CrashTiming::During => {}
482            }
483        }
484    }
485
486    fn append_wal_record(&self, wal: &mut WalWriter, record: &WalRecord) -> Result<()> {
487        #[cfg(feature = "test-hooks")]
488        {
489            let data =
490                bincode::serialize(record).map_err(|e| Error::Io(std::io::Error::other(e)))?;
491            self.notify_wal_hooks(&data, CrashTiming::Before)?;
492            self.notify_wal_hooks(&data, CrashTiming::During)?;
493            wal.append(record)?;
494            self.notify_wal_hooks(&data, CrashTiming::After)?;
495            Ok(())
496        }
497
498        #[cfg(not(feature = "test-hooks"))]
499        {
500            wal.append(record)
501        }
502    }
503
504    fn write_wal(&self, txn_id: TxnId, writes: &BTreeMap<Key, Option<Value>>) -> Result<()> {
505        if let Some(wal_lock) = &self.state.wal_writer {
506            let mut wal = wal_lock.write().unwrap();
507            self.append_wal_record(&mut wal, &WalRecord::Begin(txn_id))?;
508            for (key, value) in writes {
509                let record = match value {
510                    Some(v) => WalRecord::Put(txn_id, key.clone(), v.clone()),
511                    None => WalRecord::Delete(txn_id, key.clone()),
512                };
513                self.append_wal_record(&mut wal, &record)?;
514            }
515            self.append_wal_record(&mut wal, &WalRecord::Commit(txn_id))?;
516            // Durability point: fsync once at the commit boundary so the whole
517            // transaction (Begin..Commit) reaches stable storage together. The
518            // commit is only acknowledged after this returns (CORE-5.1).
519            wal.sync()?;
520        }
521        Ok(())
522    }
523
524    /// In-memory compaction entrypoint that rebuilds the map while honoring memory limits.
525    pub fn compact_in_memory(&self) -> Result<bool> {
526        #[cfg(feature = "test-hooks")]
527        self.notify_compaction(CrashTiming::Before);
528
529        let snapshot_bytes = {
530            let data = self.state.data.read().unwrap();
531            let mut bytes = 0usize;
532            for (k, (v, _)) in data.iter() {
533                bytes = bytes.saturating_add(k.len() + v.len());
534            }
535            bytes
536        };
537
538        let executed = self.compact_with_limit(snapshot_bytes, snapshot_bytes, || {
539            let data = self.state.data.read().unwrap();
540            let mut rebuilt = BTreeMap::new();
541            for (k, (v, version)) in data.iter() {
542                rebuilt.insert(k.clone(), (v.clone(), *version));
543            }
544            drop(data);
545
546            #[cfg(feature = "test-hooks")]
547            self.notify_compaction(CrashTiming::During);
548
549            let _snapshot_writer = self.state.owned_snapshot_gate.acquire_writer();
550            let mut write_guard = self.state.data.write().unwrap();
551            *write_guard = rebuilt;
552            Ok(())
553        })?;
554
555        #[cfg(feature = "test-hooks")]
556        self.notify_compaction(CrashTiming::After);
557
558        Ok(executed)
559    }
560
561    /// Flushes the current in-memory data to an SSTable file.
562    pub fn flush(&self) -> Result<()> {
563        let Some(path) = self.state.sstable_path.as_ref() else {
564            return Ok(());
565        };
566
567        #[cfg(feature = "test-hooks")]
568        self.notify_compaction(CrashTiming::Before);
569
570        let data = self.state.data.read().unwrap();
571        let mut writer = SstableWriter::create(path)?;
572        for (key, (value, _version)) in data.iter() {
573            #[cfg(feature = "test-hooks")]
574            {
575                let mut record = Vec::with_capacity(key.len() + value.len());
576                record.extend_from_slice(key);
577                record.extend_from_slice(value);
578                self.trigger_crash(CrashOperation::SstWrite, CrashTiming::Before);
579                if let Some(hooks) = self.io_hooks() {
580                    hooks.before_sst_write(&record).map_err(Error::Io)?;
581                }
582                self.trigger_crash(CrashOperation::SstWrite, CrashTiming::During);
583            }
584
585            writer.append(key, value)?;
586
587            #[cfg(feature = "test-hooks")]
588            self.trigger_crash(CrashOperation::SstWrite, CrashTiming::After);
589        }
590        drop(data);
591
592        #[cfg(feature = "test-hooks")]
593        self.trigger_crash(CrashOperation::SstFinalize, CrashTiming::Before);
594        let _footer = writer.finish()?;
595        #[cfg(feature = "test-hooks")]
596        self.trigger_crash(CrashOperation::SstFinalize, CrashTiming::After);
597        let reader = SstableReader::open(path)?;
598        // Also emit a placeholder vector segment alongside SSTable for future vector recovery.
599        let vec_path = path.with_extension("vec");
600        write_empty_vector_segment(&vec_path)?;
601
602        let mut slot = self.state.sstable.write().unwrap();
603        *slot = Some(reader);
604
605        #[cfg(feature = "test-hooks")]
606        self.notify_compaction(CrashTiming::After);
607        Ok(())
608    }
609
610    /// Replays the WAL to restore the state of the in-memory map.
611    fn replay(&self) -> Result<()> {
612        let path = match &self.state.wal_path {
613            Some(p) => p,
614            None => return Ok(()),
615        };
616        if !path.exists() || std::fs::metadata(path)?.len() == 0 {
617            return Ok(());
618        }
619
620        let _snapshot_writer = self.state.owned_snapshot_gate.acquire_writer();
621        let mut data = self.state.data.write().unwrap();
622        let mut max_txn_id = 0;
623        let mut max_version = self.state.commit_version.load(Ordering::Acquire);
624        let reader = WalReader::new(path)?;
625        let mut pending_txns: HashMap<TxnId, Vec<(Key, Option<Value>)>> = HashMap::new();
626
627        for record_result in reader {
628            match record_result? {
629                WalRecord::Begin(txn_id) => {
630                    max_txn_id = max_txn_id.max(txn_id.0);
631                    pending_txns.entry(txn_id).or_default();
632                }
633                WalRecord::Put(txn_id, key, value) => {
634                    max_txn_id = max_txn_id.max(txn_id.0);
635                    pending_txns
636                        .entry(txn_id)
637                        .or_default()
638                        .push((key, Some(value)));
639                }
640                WalRecord::Delete(txn_id, key) => {
641                    max_txn_id = max_txn_id.max(txn_id.0);
642                    pending_txns.entry(txn_id).or_default().push((key, None));
643                }
644                WalRecord::Commit(txn_id) => {
645                    if let Some(writes) = pending_txns.remove(&txn_id) {
646                        max_version += 1;
647                        for (key, value) in writes {
648                            if let Some(v) = value {
649                                data.insert(key, (v, max_version));
650                            } else {
651                                data.remove(&key);
652                            }
653                        }
654                    }
655                }
656            }
657        }
658
659        self.state
660            .next_txn_id
661            .store(max_txn_id + 1, Ordering::SeqCst);
662        self.state
663            .commit_version
664            .store(max_version, Ordering::SeqCst);
665        Ok(())
666    }
667
668    fn load_sstable(&self) -> Result<()> {
669        let path = match &self.state.sstable_path {
670            Some(p) => p,
671            None => return Ok(()),
672        };
673        if !path.exists() {
674            return Ok(());
675        }
676
677        let mut reader = match SstableReader::open(path) {
678            Ok(reader) => reader,
679            // Crash-recovery tolerance: a truncated or corrupt SSTable means the
680            // durable segment was torn by a crash and its contents are
681            // unrecoverable. Discard the unreadable segment and continue recovery
682            // from the WAL — this mirrors `WalReader`'s torn-tail handling. Only
683            // corruption signatures are tolerated; genuine I/O faults still
684            // propagate so real failures are not masked.
685            Err(e @ (Error::InvalidFormat(_) | Error::ChecksumMismatch)) => {
686                warn!(
687                    path = %path.display(),
688                    error = %e,
689                    "discarding unreadable SSTable during recovery; replaying WAL only"
690                );
691                return Ok(());
692            }
693            Err(Error::Io(io)) if io.kind() == std::io::ErrorKind::UnexpectedEof => {
694                warn!(
695                    path = %path.display(),
696                    "discarding truncated SSTable during recovery; replaying WAL only"
697                );
698                return Ok(());
699            }
700            Err(e) => return Err(e),
701        };
702        let mut data = self.state.data.write().unwrap();
703        let mut version = self.state.commit_version.load(Ordering::Acquire);
704
705        let keys: Vec<Key> = reader
706            .index()
707            .iter()
708            .map(|entry| entry.key.clone())
709            .collect();
710
711        for key in keys {
712            if let Some(value) = reader.get(&key)? {
713                version += 1;
714                data.insert(key, (value, version));
715            }
716        }
717
718        self.state.commit_version.store(version, Ordering::SeqCst);
719        let mut slot = self.state.sstable.write().unwrap();
720        *slot = Some(reader);
721        Ok(())
722    }
723
724    /// Loads SSTable then replays WAL to restore state.
725    fn recover(&self) -> Result<()> {
726        self.load_sstable()?;
727        self.replay()?;
728        self.state.recompute_current_memory();
729        Ok(())
730    }
731
732    fn sstable_get(&self, key: &Key) -> Result<Option<Value>> {
733        let mut guard = self.state.sstable.write().unwrap();
734        if let Some(reader) = guard.as_mut() {
735            return reader.get(key);
736        }
737        Ok(None)
738    }
739
740    fn begin_internal(&self, mode: TxnMode) -> Result<MemoryTransaction<'_>> {
741        let txn_id = self.state.next_txn_id.fetch_add(1, Ordering::SeqCst);
742        let start_version = self.state.commit_version.load(Ordering::Acquire);
743        Ok(MemoryTransaction::new(
744            self,
745            TxnId(txn_id),
746            mode,
747            start_version,
748        ))
749    }
750}
751
752impl<'a> TxnManager<'a, MemoryTransaction<'a>> for &'a MemoryTxnManager {
753    fn begin(&'a self, mode: TxnMode) -> Result<MemoryTransaction<'a>> {
754        self.begin_internal(mode)
755    }
756
757    fn commit(&'a self, mut txn: MemoryTransaction<'a>) -> Result<()> {
758        if txn.state != TxnState::Active {
759            return Err(Error::TxnClosed);
760        }
761        if txn.mode == TxnMode::ReadOnly || txn.writes.is_empty() {
762            txn.state = TxnState::Committed;
763            return Ok(());
764        }
765
766        let _snapshot_writer = self.state.owned_snapshot_gate.acquire_writer();
767        let mut data = self.state.data.write().unwrap();
768
769        for key in txn.read_set.keys() {
770            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
771            if current_version > txn.start_version {
772                return Err(Error::TxnConflict);
773            }
774        }
775
776        // Detect write-write conflicts even when the key was never read.
777        for key in txn.writes.keys() {
778            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
779            if current_version > txn.start_version {
780                return Err(Error::TxnConflict);
781            }
782        }
783
784        // Compute prospective memory usage and enforce limits before mutating state.
785        let mut delta: isize = 0;
786        for (key, value) in &txn.writes {
787            let current_size = data.get(key).map(|(v, _)| key.len() + v.len()).unwrap_or(0);
788            let new_size = match value {
789                Some(v) => key.len() + v.len(),
790                None => 0,
791            };
792            delta += new_size as isize - current_size as isize;
793        }
794
795        let current_mem = self.state.current_memory.load(Ordering::Relaxed);
796        let prospective = if delta >= 0 {
797            current_mem.saturating_add(delta as usize)
798        } else {
799            current_mem.saturating_sub(delta.unsigned_abs())
800        };
801
802        if delta > 0 {
803            self.state.check_memory_limit(delta as usize)?;
804        }
805
806        let commit_version = self.state.commit_version.fetch_add(1, Ordering::AcqRel) + 1;
807
808        self.write_wal(txn.id, &txn.writes)?;
809
810        for (key, value) in std::mem::take(&mut txn.writes) {
811            if let Some(v) = value {
812                data.insert(key, (v, commit_version));
813            } else {
814                data.remove(&key);
815            }
816        }
817
818        self.state
819            .current_memory
820            .store(prospective, Ordering::Relaxed);
821
822        txn.state = TxnState::Committed;
823        Ok(())
824    }
825
826    fn rollback(&'a self, mut txn: MemoryTransaction<'a>) -> Result<()> {
827        if txn.state != TxnState::Active {
828            return Err(Error::TxnClosed);
829        }
830        txn.state = TxnState::RolledBack;
831        Ok(())
832    }
833}
834
835/// An in-memory transaction that enforces snapshot isolation.
836pub struct MemoryTransaction<'a> {
837    manager: &'a MemoryTxnManager,
838    id: TxnId,
839    mode: TxnMode,
840    state: TxnState,
841    start_version: u64,
842    writes: BTreeMap<Key, Option<Value>>,
843    read_set: HashMap<Key, u64>,
844}
845
846impl<'a> MemoryTransaction<'a> {
847    fn new(manager: &'a MemoryTxnManager, id: TxnId, mode: TxnMode, start_version: u64) -> Self {
848        Self {
849            manager,
850            id,
851            mode,
852            state: TxnState::Active,
853            start_version,
854            writes: BTreeMap::new(),
855            read_set: HashMap::new(),
856        }
857    }
858
859    fn ensure_active(&self) -> Result<()> {
860        if self.state != TxnState::Active {
861            return Err(Error::TxnClosed);
862        }
863        Ok(())
864    }
865
866    /// トランザクションを消費せずにロールバックする。
867    pub(crate) fn rollback_in_place(&mut self) -> Result<()> {
868        if self.state != TxnState::Active {
869            return Err(Error::TxnClosed);
870        }
871        self.state = TxnState::RolledBack;
872        Ok(())
873    }
874
875    fn scan_range_internal(&mut self, start: &[u8], end: &[u8]) -> MergedScanIter<'_> {
876        let start_vec = start.to_vec();
877        let end_vec = end.to_vec();
878        let data_guard = self.manager.state.data.read().unwrap();
879        let data_ptr: *const BTreeMap<Key, VersionedValue> = &*data_guard;
880        let data_iter = unsafe {
881            // Safety: data_guard keeps the map alive for the lifetime of the iterator.
882            (&*data_ptr).range((Included(start_vec.clone()), Excluded(end_vec.clone())))
883        };
884        let write_iter = self
885            .writes
886            .range((Included(start_vec.clone()), Excluded(end_vec.clone())));
887
888        MergedScanIter::new(
889            data_guard,
890            data_iter,
891            write_iter,
892            None,
893            Some(end_vec),
894            self.start_version,
895            &mut self.read_set,
896        )
897    }
898
899    fn scan_prefix_internal(&mut self, prefix: &[u8]) -> MergedScanIter<'_> {
900        let prefix_vec = prefix.to_vec();
901        let data_guard = self.manager.state.data.read().unwrap();
902        let data_ptr: *const BTreeMap<Key, VersionedValue> = &*data_guard;
903        let data_iter = unsafe {
904            // Safety: data_guard keeps the map alive for the lifetime of the iterator.
905            (&*data_ptr).range(prefix_vec.clone()..)
906        };
907        let write_iter = self.writes.range(prefix_vec.clone()..);
908        MergedScanIter::new(
909            data_guard,
910            data_iter,
911            write_iter,
912            Some(prefix_vec),
913            None,
914            self.start_version,
915            &mut self.read_set,
916        )
917    }
918
919    fn scan_from_internal(&mut self, start: &[u8]) -> MergedScanIter<'_> {
920        let start_vec = start.to_vec();
921        let data_guard = self.manager.state.data.read().unwrap();
922        let data_ptr: *const BTreeMap<Key, VersionedValue> = &*data_guard;
923        let data_iter = unsafe {
924            // Safety: data_guard keeps the map alive for the lifetime of the iterator.
925            (&*data_ptr).range(start_vec.clone()..)
926        };
927        let write_iter = self.writes.range(start_vec..);
928        MergedScanIter::new(
929            data_guard,
930            data_iter,
931            write_iter,
932            None,
933            None,
934            self.start_version,
935            &mut self.read_set,
936        )
937    }
938}
939
940impl<'a> KVTransaction<'a> for MemoryTransaction<'a> {
941    fn id(&self) -> TxnId {
942        self.id
943    }
944
945    fn mode(&self) -> TxnMode {
946        self.mode
947    }
948
949    fn get(&mut self, key: &Key) -> Result<Option<Value>> {
950        if self.state != TxnState::Active {
951            return Err(Error::TxnClosed);
952        }
953
954        if let Some(value) = self.writes.get(key) {
955            return Ok(value.clone());
956        }
957
958        let result = {
959            let data = self.manager.state.data.read().unwrap();
960            data.get(key).cloned()
961        };
962
963        if let Some((v, version)) = result {
964            self.read_set.insert(key.clone(), version);
965            return Ok(Some(v));
966        }
967
968        // Read-through to SSTable if not found in memory.
969        if let Some(value) = self.manager.sstable_get(key)? {
970            let version = self.manager.state.commit_version.load(Ordering::Acquire);
971            self.read_set.insert(key.clone(), version);
972            return Ok(Some(value));
973        }
974
975        Ok(None)
976    }
977
978    fn put(&mut self, key: Key, value: Value) -> Result<()> {
979        if self.state != TxnState::Active {
980            return Err(Error::TxnClosed);
981        }
982        if self.mode == TxnMode::ReadOnly {
983            return Err(Error::TxnReadOnly);
984        }
985        self.writes.insert(key, Some(value));
986        Ok(())
987    }
988
989    fn delete(&mut self, key: Key) -> Result<()> {
990        if self.state != TxnState::Active {
991            return Err(Error::TxnClosed);
992        }
993        if self.mode == TxnMode::ReadOnly {
994            return Err(Error::TxnReadOnly);
995        }
996        self.writes.insert(key, None);
997        Ok(())
998    }
999
1000    fn scan_prefix(
1001        &mut self,
1002        prefix: &[u8],
1003    ) -> Result<Box<dyn Iterator<Item = (Key, Value)> + '_>> {
1004        self.ensure_active()?;
1005        let iter = self
1006            .scan_prefix_internal(prefix)
1007            .filter_map(|(k, v)| v.map(|val| (k, val)));
1008        Ok(Box::new(iter))
1009    }
1010
1011    fn scan_range(
1012        &mut self,
1013        start: &[u8],
1014        end: &[u8],
1015    ) -> Result<Box<dyn Iterator<Item = (Key, Value)> + '_>> {
1016        self.ensure_active()?;
1017        let iter = self
1018            .scan_range_internal(start, end)
1019            .filter_map(|(k, v)| v.map(|val| (k, val)));
1020        Ok(Box::new(iter))
1021    }
1022
1023    fn scan_from(&mut self, start: &[u8]) -> Result<Box<dyn Iterator<Item = (Key, Value)> + '_>> {
1024        self.ensure_active()?;
1025        let iter = self
1026            .scan_from_internal(start)
1027            .filter_map(|(key, value)| value.map(|value| (key, value)));
1028        Ok(Box::new(iter))
1029    }
1030
1031    fn commit_self(mut self) -> Result<()> {
1032        if self.state != TxnState::Active {
1033            return Err(Error::TxnClosed);
1034        }
1035        if self.mode == TxnMode::ReadOnly || self.writes.is_empty() {
1036            self.state = TxnState::Committed;
1037            return Ok(());
1038        }
1039
1040        let _snapshot_writer = self.manager.state.owned_snapshot_gate.acquire_writer();
1041        let mut data = self.manager.state.data.write().unwrap();
1042
1043        // Check read-set for conflicts
1044        for key in self.read_set.keys() {
1045            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
1046            if current_version > self.start_version {
1047                return Err(Error::TxnConflict);
1048            }
1049        }
1050
1051        // Check write-write conflicts
1052        for key in self.writes.keys() {
1053            let current_version = data.get(key).map(|(_, v)| *v).unwrap_or(0);
1054            if current_version > self.start_version {
1055                return Err(Error::TxnConflict);
1056            }
1057        }
1058
1059        // Compute prospective memory usage
1060        let mut delta: isize = 0;
1061        for (key, value) in &self.writes {
1062            let current_size = data.get(key).map(|(v, _)| key.len() + v.len()).unwrap_or(0);
1063            let new_size = match value {
1064                Some(v) => key.len() + v.len(),
1065                None => 0,
1066            };
1067            delta += new_size as isize - current_size as isize;
1068        }
1069
1070        let current_mem = self.manager.state.current_memory.load(Ordering::Relaxed);
1071        let prospective = if delta >= 0 {
1072            current_mem.saturating_add(delta as usize)
1073        } else {
1074            current_mem.saturating_sub(delta.unsigned_abs())
1075        };
1076
1077        if delta > 0 {
1078            self.manager.state.check_memory_limit(delta as usize)?;
1079        }
1080
1081        let commit_version = self
1082            .manager
1083            .state
1084            .commit_version
1085            .fetch_add(1, Ordering::AcqRel)
1086            + 1;
1087
1088        // WAL write
1089        self.manager.write_wal(self.id, &self.writes)?;
1090
1091        // Apply writes
1092        for (key, value) in std::mem::take(&mut self.writes) {
1093            if let Some(v) = value {
1094                data.insert(key, (v, commit_version));
1095            } else {
1096                data.remove(&key);
1097            }
1098        }
1099
1100        self.manager
1101            .state
1102            .current_memory
1103            .store(prospective, Ordering::Relaxed);
1104
1105        self.state = TxnState::Committed;
1106        Ok(())
1107    }
1108
1109    fn rollback_self(mut self) -> Result<()> {
1110        if self.state != TxnState::Active {
1111            return Err(Error::TxnClosed);
1112        }
1113        self.state = TxnState::RolledBack;
1114        Ok(())
1115    }
1116}
1117
1118/// An owned MemoryKV transaction whose manager and mutable snapshot state both outlive callers.
1119struct OwnedMemoryTransaction {
1120    manager: Arc<MemoryTxnManager>,
1121    state: Arc<Mutex<OwnedMemoryTransactionState>>,
1122}
1123
1124struct OwnedMemoryTransactionState {
1125    id: TxnId,
1126    mode: TxnMode,
1127    state: TxnState,
1128    start_version: u64,
1129    writes: BTreeMap<Key, Option<Value>>,
1130    read_set: HashMap<Key, u64>,
1131    cursor_open: bool,
1132}
1133
1134impl OwnedMemoryTransaction {
1135    fn new(manager: Arc<MemoryTxnManager>, mode: TxnMode) -> Self {
1136        let id = TxnId(manager.state.next_txn_id.fetch_add(1, Ordering::SeqCst));
1137        let start_version = manager.state.commit_version.load(Ordering::Acquire);
1138        Self {
1139            manager,
1140            state: Arc::new(Mutex::new(OwnedMemoryTransactionState {
1141                id,
1142                mode,
1143                state: TxnState::Active,
1144                start_version,
1145                writes: BTreeMap::new(),
1146                read_set: HashMap::new(),
1147                cursor_open: false,
1148            })),
1149        }
1150    }
1151
1152    fn open_cursor(
1153        &mut self,
1154        start: Option<Key>,
1155        prefix: Option<Vec<u8>>,
1156        end: Option<Key>,
1157    ) -> Result<Box<dyn OwnedKVScan>> {
1158        let snapshot = self.manager.state.owned_snapshot_gate.acquire_reader();
1159        let mut state = self
1160            .state
1161            .lock()
1162            .expect("owned memory transaction mutex poisoned");
1163        if state.state != TxnState::Active || state.cursor_open {
1164            return Err(Error::TxnClosed);
1165        }
1166        state.cursor_open = true;
1167        drop(state);
1168        Ok(Box::new(OwnedMemoryCursor {
1169            manager: self.manager.clone(),
1170            transaction: self.state.clone(),
1171            snapshot: Some(snapshot),
1172            last_key: None,
1173            start,
1174            prefix,
1175            end,
1176        }))
1177    }
1178
1179    fn ensure_active(state: &OwnedMemoryTransactionState) -> Result<()> {
1180        if state.state != TxnState::Active {
1181            return Err(Error::TxnClosed);
1182        }
1183        Ok(())
1184    }
1185}
1186
1187impl OwnedKVTransaction for OwnedMemoryTransaction {
1188    fn id(&self) -> TxnId {
1189        self.state
1190            .lock()
1191            .expect("owned memory transaction mutex poisoned")
1192            .id
1193    }
1194
1195    fn mode(&self) -> TxnMode {
1196        self.state
1197            .lock()
1198            .expect("owned memory transaction mutex poisoned")
1199            .mode
1200    }
1201
1202    fn get(&mut self, key: &Key) -> Result<Option<Value>> {
1203        let mut state = self
1204            .state
1205            .lock()
1206            .expect("owned memory transaction mutex poisoned");
1207        Self::ensure_active(&state)?;
1208        if let Some(value) = state.writes.get(key) {
1209            return Ok(value.clone());
1210        }
1211
1212        let result = {
1213            let data = self.manager.state.data.read().unwrap();
1214            data.get(key).cloned()
1215        };
1216        if let Some((value, version)) = result {
1217            if version <= state.start_version {
1218                state.read_set.insert(key.clone(), version);
1219                return Ok(Some(value));
1220            }
1221            return Ok(None);
1222        }
1223
1224        if let Some(value) = self.manager.sstable_get(key)? {
1225            let start_version = state.start_version;
1226            state.read_set.insert(key.clone(), start_version);
1227            return Ok(Some(value));
1228        }
1229        Ok(None)
1230    }
1231
1232    fn put(&mut self, key: Key, value: Value) -> Result<()> {
1233        let mut state = self
1234            .state
1235            .lock()
1236            .expect("owned memory transaction mutex poisoned");
1237        Self::ensure_active(&state)?;
1238        if state.mode == TxnMode::ReadOnly {
1239            return Err(Error::TxnReadOnly);
1240        }
1241        if state.cursor_open {
1242            return Err(Error::TxnClosed);
1243        }
1244        state.writes.insert(key, Some(value));
1245        Ok(())
1246    }
1247
1248    fn delete(&mut self, key: Key) -> Result<()> {
1249        let mut state = self
1250            .state
1251            .lock()
1252            .expect("owned memory transaction mutex poisoned");
1253        Self::ensure_active(&state)?;
1254        if state.mode == TxnMode::ReadOnly {
1255            return Err(Error::TxnReadOnly);
1256        }
1257        if state.cursor_open {
1258            return Err(Error::TxnClosed);
1259        }
1260        state.writes.insert(key, None);
1261        Ok(())
1262    }
1263
1264    fn scan_prefix(&mut self, prefix: &[u8]) -> Result<Box<dyn OwnedKVScan>> {
1265        // Start at the prefix itself.  An owned cursor advances over the backing map rather
1266        // than a borrowed `BTreeMap::range` guard; starting at keyspace origin would see an
1267        // unrelated earlier key (for example persisted catalog metadata), classify it as out
1268        // of scope, and incorrectly report an empty prefix scan.
1269        self.open_cursor(Some(prefix.to_vec()), Some(prefix.to_vec()), None)
1270    }
1271
1272    fn scan_range(&mut self, start: &[u8], end: &[u8]) -> Result<Box<dyn OwnedKVScan>> {
1273        self.open_cursor(Some(start.to_vec()), None, Some(end.to_vec()))
1274    }
1275
1276    fn scan_from(&mut self, start: &[u8]) -> Result<Box<dyn OwnedKVScan>> {
1277        self.open_cursor(Some(start.to_vec()), None, None)
1278    }
1279
1280    fn commit(self: Box<Self>) -> Result<()> {
1281        let (id, mode, start_version, writes, read_set) = {
1282            let mut state = self
1283                .state
1284                .lock()
1285                .expect("owned memory transaction mutex poisoned");
1286            Self::ensure_active(&state)?;
1287            if state.cursor_open {
1288                return Err(Error::TxnClosed);
1289            }
1290            state.state = TxnState::Committed;
1291            (
1292                state.id,
1293                state.mode,
1294                state.start_version,
1295                std::mem::take(&mut state.writes),
1296                std::mem::take(&mut state.read_set),
1297            )
1298        };
1299        if mode == TxnMode::ReadOnly || writes.is_empty() {
1300            return Ok(());
1301        }
1302
1303        let _snapshot_writer = self.manager.state.owned_snapshot_gate.acquire_writer();
1304        let mut data = self.manager.state.data.write().unwrap();
1305        for key in read_set.keys().chain(writes.keys()) {
1306            let current_version = data.get(key).map(|(_, version)| *version).unwrap_or(0);
1307            if current_version > start_version {
1308                return Err(Error::TxnConflict);
1309            }
1310        }
1311
1312        let mut delta: isize = 0;
1313        for (key, value) in &writes {
1314            let current_size = data
1315                .get(key)
1316                .map(|(value, _)| key.len() + value.len())
1317                .unwrap_or(0);
1318            let new_size = value.as_ref().map_or(0, |value| key.len() + value.len());
1319            delta += new_size as isize - current_size as isize;
1320        }
1321        let current_memory = self.manager.state.current_memory.load(Ordering::Relaxed);
1322        let prospective = if delta >= 0 {
1323            current_memory.saturating_add(delta as usize)
1324        } else {
1325            current_memory.saturating_sub(delta.unsigned_abs())
1326        };
1327        if delta > 0 {
1328            self.manager.state.check_memory_limit(delta as usize)?;
1329        }
1330
1331        let commit_version = self
1332            .manager
1333            .state
1334            .commit_version
1335            .fetch_add(1, Ordering::AcqRel)
1336            + 1;
1337        self.manager.write_wal(id, &writes)?;
1338        for (key, value) in writes {
1339            if let Some(value) = value {
1340                data.insert(key, (value, commit_version));
1341            } else {
1342                data.remove(&key);
1343            }
1344        }
1345        self.manager
1346            .state
1347            .current_memory
1348            .store(prospective, Ordering::Relaxed);
1349        Ok(())
1350    }
1351
1352    fn rollback(self: Box<Self>) -> Result<()> {
1353        let mut state = self
1354            .state
1355            .lock()
1356            .expect("owned memory transaction mutex poisoned");
1357        Self::ensure_active(&state)?;
1358        if state.cursor_open {
1359            return Err(Error::TxnClosed);
1360        }
1361        state.writes.clear();
1362        state.state = TxnState::RolledBack;
1363        Ok(())
1364    }
1365}
1366
1367struct OwnedMemoryCursor {
1368    manager: Arc<MemoryTxnManager>,
1369    transaction: Arc<Mutex<OwnedMemoryTransactionState>>,
1370    snapshot: Option<OwnedSnapshotReader>,
1371    last_key: Option<Key>,
1372    start: Option<Key>,
1373    prefix: Option<Vec<u8>>,
1374    end: Option<Key>,
1375}
1376
1377impl OwnedMemoryCursor {
1378    fn key_is_in_scope(&self, key: &Key) -> bool {
1379        self.prefix
1380            .as_ref()
1381            .is_none_or(|prefix| key.starts_with(prefix))
1382            && self.end.as_ref().is_none_or(|end| key < end)
1383    }
1384
1385    fn data_candidate(&self, start_version: u64) -> Option<(Key, Value, u64)> {
1386        let data = self.manager.state.data.read().unwrap();
1387        let entries: Box<dyn Iterator<Item = (&Key, &(Value, u64))>> = match &self.last_key {
1388            Some(last_key) => Box::new(data.range::<Key, _>((Excluded(last_key), Unbounded))),
1389            None => match &self.start {
1390                Some(start) => Box::new(data.range::<Key, _>((Included(start), Unbounded))),
1391                None => Box::new(data.iter()),
1392            },
1393        };
1394        for (key, (value, version)) in entries {
1395            if !self.key_is_in_scope(key) {
1396                return None;
1397            }
1398            if *version <= start_version {
1399                return Some((key.clone(), value.clone(), *version));
1400            }
1401        }
1402        None
1403    }
1404
1405    fn write_candidate(&self) -> Option<(Key, Option<Value>)> {
1406        let transaction = self
1407            .transaction
1408            .lock()
1409            .expect("owned memory transaction mutex poisoned");
1410        let entry = match &self.last_key {
1411            Some(last_key) => transaction
1412                .writes
1413                .range::<Key, _>((Excluded(last_key), Unbounded))
1414                .next(),
1415            None => match &self.start {
1416                Some(start) => transaction
1417                    .writes
1418                    .range::<Key, _>((Included(start), Unbounded))
1419                    .next(),
1420                None => transaction.writes.iter().next(),
1421            },
1422        }?;
1423        self.key_is_in_scope(entry.0)
1424            .then(|| (entry.0.clone(), entry.1.clone()))
1425    }
1426
1427    fn record_read(&self, key: Key, version: u64) -> Result<()> {
1428        let mut transaction = self
1429            .transaction
1430            .lock()
1431            .expect("owned memory transaction mutex poisoned");
1432        if transaction.state != TxnState::Active || !transaction.cursor_open {
1433            return Err(Error::TxnClosed);
1434        }
1435        transaction.read_set.insert(key, version);
1436        Ok(())
1437    }
1438
1439    fn finish(&mut self) {
1440        self.snapshot.take();
1441        let mut transaction = self
1442            .transaction
1443            .lock()
1444            .expect("owned memory transaction mutex poisoned");
1445        transaction.cursor_open = false;
1446    }
1447}
1448
1449impl OwnedKVScan for OwnedMemoryCursor {
1450    fn next_entry(&mut self) -> Result<Option<(Key, Value)>> {
1451        if self.snapshot.is_none() {
1452            return Ok(None);
1453        }
1454        loop {
1455            let start_version = self
1456                .transaction
1457                .lock()
1458                .expect("owned memory transaction mutex poisoned")
1459                .start_version;
1460            let data = self.data_candidate(start_version);
1461            let write = self.write_candidate();
1462            let next = match (data, write) {
1463                (Some((data_key, data_value, data_version)), Some((write_key, write_value))) => {
1464                    if data_key == write_key {
1465                        self.record_read(data_key.clone(), data_version)?;
1466                        (data_key, write_value)
1467                    } else if data_key < write_key {
1468                        self.record_read(data_key.clone(), data_version)?;
1469                        (data_key, Some(data_value))
1470                    } else {
1471                        (write_key, write_value)
1472                    }
1473                }
1474                (Some((data_key, data_value, data_version)), None) => {
1475                    self.record_read(data_key.clone(), data_version)?;
1476                    (data_key, Some(data_value))
1477                }
1478                (None, Some((write_key, write_value))) => (write_key, write_value),
1479                (None, None) => {
1480                    self.finish();
1481                    return Ok(None);
1482                }
1483            };
1484            self.last_key = Some(next.0.clone());
1485            if let Some(value) = next.1 {
1486                return Ok(Some((next.0, value)));
1487            }
1488        }
1489    }
1490
1491    fn close(&mut self) -> Result<()> {
1492        self.finish();
1493        Ok(())
1494    }
1495}
1496
1497impl Drop for OwnedMemoryCursor {
1498    fn drop(&mut self) {
1499        self.finish();
1500    }
1501}
1502
1503/// Lazy merge iterator that overlays in-flight writes onto a snapshot guard.
1504struct MergedScanIter<'a> {
1505    _data_guard: RwLockReadGuard<'a, BTreeMap<Key, VersionedValue>>,
1506    data_iter: std::collections::btree_map::Range<'a, Key, VersionedValue>,
1507    write_iter: std::collections::btree_map::Range<'a, Key, Option<Value>>,
1508    data_peek: Option<(Key, (Value, u64))>,
1509    write_peek: Option<(Key, Option<Value>)>,
1510    prefix: Option<Vec<u8>>,
1511    end: Option<Key>,
1512    start_version: u64,
1513    read_set: &'a mut HashMap<Key, u64>,
1514}
1515
1516impl<'a> MergedScanIter<'a> {
1517    #[allow(clippy::too_many_arguments)]
1518    fn new(
1519        data_guard: std::sync::RwLockReadGuard<'a, BTreeMap<Key, VersionedValue>>,
1520        data_iter: std::collections::btree_map::Range<'a, Key, VersionedValue>,
1521        write_iter: std::collections::btree_map::Range<'a, Key, Option<Value>>,
1522        prefix: Option<Vec<u8>>,
1523        end: Option<Key>,
1524        start_version: u64,
1525        read_set: &'a mut HashMap<Key, u64>,
1526    ) -> Self {
1527        let mut iter = Self {
1528            _data_guard: data_guard,
1529            data_iter,
1530            write_iter,
1531            data_peek: None,
1532            write_peek: None,
1533            prefix,
1534            end,
1535            start_version,
1536            read_set,
1537        };
1538        iter.advance_data();
1539        iter.advance_write();
1540        iter
1541    }
1542
1543    fn advance_data(&mut self) {
1544        self.data_peek = None;
1545        while let Some((k, (v, ver))) = self.data_iter.next().map(|(k, v)| (k.clone(), v.clone())) {
1546            if let Some(end) = &self.end {
1547                if k >= *end {
1548                    return;
1549                }
1550            }
1551            if let Some(prefix) = &self.prefix {
1552                if !k.starts_with(prefix) {
1553                    return;
1554                }
1555            }
1556            if ver > self.start_version {
1557                continue;
1558            }
1559            self.data_peek = Some((k, (v, ver)));
1560            return;
1561        }
1562    }
1563
1564    fn advance_write(&mut self) {
1565        self.write_peek = None;
1566        if let Some((k, v)) = self.write_iter.next().map(|(k, v)| (k.clone(), v.clone())) {
1567            if let Some(end) = &self.end {
1568                if k >= *end {
1569                    return;
1570                }
1571            }
1572            if let Some(prefix) = &self.prefix {
1573                if !k.starts_with(prefix) {
1574                    return;
1575                }
1576            }
1577            self.write_peek = Some((k, v));
1578        }
1579    }
1580}
1581
1582impl<'a> Iterator for MergedScanIter<'a> {
1583    type Item = (Key, Option<Value>);
1584
1585    fn next(&mut self) -> Option<Self::Item> {
1586        let data_key = self.data_peek.as_ref().map(|(k, _)| k.clone());
1587        let write_key = self.write_peek.as_ref().map(|(k, _)| k.clone());
1588
1589        match (data_key, write_key) {
1590            (Some(dk), Some(wk)) => {
1591                if dk == wk {
1592                    let (_, (_, ver)) = self.data_peek.take().unwrap();
1593                    let (_, write_val) = self.write_peek.take().unwrap();
1594                    self.read_set.insert(dk.clone(), ver);
1595                    self.advance_data();
1596                    self.advance_write();
1597                    Some((dk, write_val))
1598                } else if dk < wk {
1599                    let (k, (v, ver)) = self.data_peek.take().unwrap();
1600                    self.read_set.insert(k.clone(), ver);
1601                    self.advance_data();
1602                    Some((k, Some(v)))
1603                } else {
1604                    let (k, write_val) = self.write_peek.take().unwrap();
1605                    self.advance_write();
1606                    Some((k, write_val))
1607                }
1608            }
1609            (Some(_), None) => {
1610                let (k, (v, ver)) = self.data_peek.take().unwrap();
1611                self.read_set.insert(k.clone(), ver);
1612                self.advance_data();
1613                Some((k, Some(v)))
1614            }
1615            (None, Some(_)) => {
1616                let (k, write_val) = self.write_peek.take().unwrap();
1617                self.advance_write();
1618                Some((k, write_val))
1619            }
1620            (None, None) => None,
1621        }
1622    }
1623}
1624
1625impl<'a> Drop for MemoryTransaction<'a> {
1626    fn drop(&mut self) {
1627        if self.state == TxnState::Active {
1628            self.state = TxnState::RolledBack;
1629        }
1630    }
1631}
1632
1633#[cfg(all(test, not(target_arch = "wasm32")))]
1634mod tests {
1635    use super::*;
1636    use crate::{KVTransaction, TxnManager};
1637    use tempfile::tempdir;
1638    use tracing::Level;
1639
1640    fn key(s: &str) -> Key {
1641        s.as_bytes().to_vec()
1642    }
1643
1644    fn value(s: &str) -> Value {
1645        s.as_bytes().to_vec()
1646    }
1647
1648    fn committed_value_after_reopen(wal_path: &Path, key: Key) -> Option<Value> {
1649        let reopened = MemoryKV::open(wal_path).unwrap();
1650        let manager = reopened.txn_manager();
1651        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1652        txn.get(&key).unwrap()
1653    }
1654
1655    fn write_flush_and_corrupt_sstable<F>(corrupt: F) -> (tempfile::TempDir, PathBuf)
1656    where
1657        F: FnOnce(&Path),
1658    {
1659        let dir = tempdir().unwrap();
1660        let wal_path = dir.path().join("wal.log");
1661        {
1662            let store = MemoryKV::open(&wal_path).unwrap();
1663            let manager = store.txn_manager();
1664            let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1665            txn.put(key("k1"), value("v1")).unwrap();
1666            manager.commit(txn).unwrap();
1667            store.flush().unwrap();
1668        }
1669
1670        corrupt(&wal_path.with_extension("sst"));
1671        (dir, wal_path)
1672    }
1673
1674    #[cfg(feature = "test-hooks")]
1675    struct FailsBeforeFsync;
1676
1677    #[cfg(feature = "test-hooks")]
1678    impl IoHooks for FailsBeforeFsync {
1679        fn before_fsync(&self) -> std::io::Result<()> {
1680            Err(std::io::Error::other("injected WAL fsync failure"))
1681        }
1682    }
1683
1684    #[cfg(feature = "test-hooks")]
1685    #[test]
1686    fn commit_self_wal_fsync_failure_does_not_ack_or_apply() {
1687        let dir = tempdir().unwrap();
1688        let wal_path = dir.path().join("wal.log");
1689        let store = MemoryKV::open_with_io_hooks(&wal_path, Arc::new(FailsBeforeFsync)).unwrap();
1690        let manager = store.txn_manager();
1691
1692        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1693        txn.put(key("not-acked"), value("value")).unwrap();
1694        let result = txn.commit_self();
1695        assert!(matches!(result, Err(Error::Io(_))));
1696
1697        let mut read_txn = manager.begin(TxnMode::ReadOnly).unwrap();
1698        assert_eq!(read_txn.get(&key("not-acked")).unwrap(), None);
1699    }
1700
1701    #[test]
1702    fn test_put_and_get_transient() {
1703        let store = MemoryKV::new();
1704        let manager = store.txn_manager();
1705        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1706        txn.put(key("hello"), value("world")).unwrap();
1707        let val = txn.get(&key("hello")).unwrap();
1708        assert_eq!(val, Some(value("world")));
1709        manager.commit(txn).unwrap();
1710
1711        let mut txn2 = manager.begin(TxnMode::ReadOnly).unwrap();
1712        let val2 = txn2.get(&key("hello")).unwrap();
1713        assert_eq!(val2, Some(value("world")));
1714    }
1715
1716    #[test]
1717    fn test_occ_conflict() {
1718        let store = MemoryKV::new();
1719        let manager = store.txn_manager();
1720
1721        let mut t1 = manager.begin(TxnMode::ReadWrite).unwrap();
1722        t1.get(&key("k1")).unwrap();
1723
1724        let mut t2 = manager.begin(TxnMode::ReadWrite).unwrap();
1725        t2.put(key("k1"), value("v2")).unwrap();
1726        assert!(manager.commit(t2).is_ok());
1727
1728        t1.put(key("k1"), value("v1")).unwrap();
1729        let result = manager.commit(t1);
1730        assert!(matches!(result, Err(Error::TxnConflict)));
1731    }
1732
1733    #[test]
1734    fn test_blind_write_conflict() {
1735        let store = MemoryKV::new();
1736        let manager = store.txn_manager();
1737
1738        let mut t1 = manager.begin(TxnMode::ReadWrite).unwrap();
1739        t1.put(key("k1"), value("v1")).unwrap();
1740
1741        let mut t2 = manager.begin(TxnMode::ReadWrite).unwrap();
1742        t2.put(key("k1"), value("v2")).unwrap();
1743        assert!(manager.commit(t2).is_ok());
1744
1745        let result = manager.commit(t1);
1746        assert!(matches!(result, Err(Error::TxnConflict)));
1747    }
1748
1749    #[test]
1750    fn test_read_only_write_fails() {
1751        let store = MemoryKV::new();
1752        let manager = store.txn_manager();
1753        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1754        assert!(matches!(
1755            txn.put(key("k1"), value("v1")),
1756            Err(Error::TxnReadOnly)
1757        ));
1758        assert!(matches!(txn.delete(key("k1")), Err(Error::TxnReadOnly)));
1759    }
1760
1761    #[test]
1762    fn test_txn_closed_error() {
1763        let store = MemoryKV::new();
1764        let manager = store.txn_manager();
1765        let txn = manager.begin(TxnMode::ReadWrite).unwrap();
1766        manager.commit(txn).unwrap();
1767
1768        // This is tricky to test because commit takes ownership.
1769        // We can test by creating a new txn and manually setting its state.
1770        let mut closed_txn = manager.begin(TxnMode::ReadWrite).unwrap();
1771        closed_txn.state = TxnState::Committed;
1772        assert!(matches!(closed_txn.get(&key("k1")), Err(Error::TxnClosed)));
1773        assert!(matches!(
1774            closed_txn.put(key("k1"), value("v1")),
1775            Err(Error::TxnClosed)
1776        ));
1777    }
1778
1779    #[test]
1780    fn test_get_not_found() {
1781        let store = MemoryKV::new();
1782        let manager = store.txn_manager();
1783        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1784        let res = txn.get(&key("non-existent"));
1785        assert!(res.is_ok());
1786        assert!(res.unwrap().is_none());
1787    }
1788
1789    #[test]
1790    fn flush_and_reopen_reads_from_sstable() {
1791        let dir = tempdir().unwrap();
1792        let wal_path = dir.path().join("wal.log");
1793        {
1794            let store = MemoryKV::open(&wal_path).unwrap();
1795            let manager = store.txn_manager();
1796            let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1797            txn.put(key("k1"), value("v1")).unwrap();
1798            manager.commit(txn).unwrap();
1799            store.flush().unwrap();
1800        }
1801
1802        let reopened = MemoryKV::open(&wal_path).unwrap();
1803        let manager = reopened.txn_manager();
1804        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1805        assert_eq!(txn.get(&key("k1")).unwrap(), Some(value("v1")));
1806    }
1807
1808    #[test]
1809    fn corrupt_sstable_header_is_discarded_and_wal_recovers() {
1810        let (_dir, wal_path) = write_flush_and_corrupt_sstable(|sst_path| {
1811            let mut file = std::fs::OpenOptions::new()
1812                .write(true)
1813                .open(sst_path)
1814                .unwrap();
1815            use std::io::{Seek, Write};
1816            file.seek(std::io::SeekFrom::Start(0)).unwrap();
1817            file.write_all(b"BAD!").unwrap();
1818            file.sync_all().unwrap();
1819        });
1820        let err = SstableReader::open(&wal_path.with_extension("sst")).unwrap_err();
1821        assert!(matches!(err, Error::InvalidFormat(_)));
1822
1823        assert_eq!(
1824            committed_value_after_reopen(&wal_path, key("k1")),
1825            Some(value("v1"))
1826        );
1827    }
1828
1829    #[test]
1830    fn corrupt_sstable_payload_checksum_is_discarded_and_wal_recovers() {
1831        let (_dir, wal_path) = write_flush_and_corrupt_sstable(|sst_path| {
1832            let mut file = std::fs::OpenOptions::new()
1833                .read(true)
1834                .write(true)
1835                .open(sst_path)
1836                .unwrap();
1837            use std::io::{Read, Seek, Write};
1838            file.seek(std::io::SeekFrom::Start(16 + 8 + key("k1").len() as u64))
1839                .unwrap();
1840            let mut byte = [0u8; 1];
1841            file.read_exact(&mut byte).unwrap();
1842            file.seek(std::io::SeekFrom::Current(-1)).unwrap();
1843            file.write_all(&[byte[0] ^ 0xFF]).unwrap();
1844            file.sync_all().unwrap();
1845        });
1846        let err = SstableReader::open(&wal_path.with_extension("sst")).unwrap_err();
1847        assert!(matches!(err, Error::ChecksumMismatch));
1848
1849        assert_eq!(
1850            committed_value_after_reopen(&wal_path, key("k1")),
1851            Some(value("v1"))
1852        );
1853    }
1854
1855    #[test]
1856    fn truncated_sstable_is_discarded_and_wal_recovers() {
1857        let (_dir, wal_path) = write_flush_and_corrupt_sstable(|sst_path| {
1858            let file = std::fs::OpenOptions::new()
1859                .write(true)
1860                .open(sst_path)
1861                .unwrap();
1862            file.set_len(16).unwrap();
1863            file.sync_all().unwrap();
1864        });
1865        let err = SstableReader::open(&wal_path.with_extension("sst")).unwrap_err();
1866        assert!(matches!(err, Error::InvalidFormat(_)));
1867
1868        assert_eq!(
1869            committed_value_after_reopen(&wal_path, key("k1")),
1870            Some(value("v1"))
1871        );
1872    }
1873
1874    #[test]
1875    fn wal_recovers_committed_tombstone_on_reopen() {
1876        let dir = tempdir().unwrap();
1877        let wal_path = dir.path().join("wal.log");
1878        {
1879            let store = MemoryKV::open(&wal_path).unwrap();
1880            let manager = store.txn_manager();
1881            let mut put_txn = manager.begin(TxnMode::ReadWrite).unwrap();
1882            put_txn.put(key("deleted"), value("value")).unwrap();
1883            manager.commit(put_txn).unwrap();
1884
1885            let mut delete_txn = manager.begin(TxnMode::ReadWrite).unwrap();
1886            delete_txn.delete(key("deleted")).unwrap();
1887            manager.commit(delete_txn).unwrap();
1888        }
1889
1890        assert_eq!(
1891            committed_value_after_reopen(&wal_path, key("deleted")),
1892            None
1893        );
1894    }
1895
1896    #[test]
1897    fn wal_overlays_sstable_on_reopen() {
1898        let dir = tempdir().unwrap();
1899        let wal_path = dir.path().join("wal.log");
1900        {
1901            let store = MemoryKV::open(&wal_path).unwrap();
1902            let manager = store.txn_manager();
1903            let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1904            txn.put(key("k1"), value("v1")).unwrap();
1905            manager.commit(txn).unwrap();
1906            store.flush().unwrap();
1907
1908            let mut txn2 = manager.begin(TxnMode::ReadWrite).unwrap();
1909            txn2.put(key("k1"), value("v2")).unwrap();
1910            manager.commit(txn2).unwrap();
1911        }
1912
1913        let reopened = MemoryKV::open(&wal_path).unwrap();
1914        let manager = reopened.txn_manager();
1915        let mut txn = manager.begin(TxnMode::ReadOnly).unwrap();
1916        assert_eq!(txn.get(&key("k1")).unwrap(), Some(value("v2")));
1917    }
1918
1919    #[test]
1920    fn scan_prefix_merges_snapshot_and_writes() {
1921        let store = MemoryKV::new();
1922        let manager = store.txn_manager();
1923
1924        let mut seed = manager.begin(TxnMode::ReadWrite).unwrap();
1925        seed.put(key("p:1"), value("old1")).unwrap();
1926        seed.put(key("p:2"), value("old2")).unwrap();
1927        seed.put(key("q:1"), value("other")).unwrap();
1928        manager.commit(seed).unwrap();
1929
1930        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
1931        txn.put(key("p:1"), value("new1")).unwrap();
1932        txn.delete(key("p:2")).unwrap();
1933        txn.put(key("p:3"), value("new3")).unwrap();
1934
1935        let results: Vec<_> = txn.scan_prefix(b"p:").unwrap().collect();
1936        assert_eq!(
1937            results,
1938            vec![(key("p:1"), value("new1")), (key("p:3"), value("new3"))]
1939        );
1940    }
1941
1942    #[test]
1943    fn scan_range_skips_newer_versions() {
1944        let store = MemoryKV::new();
1945        let manager = store.txn_manager();
1946
1947        let mut seed = manager.begin(TxnMode::ReadWrite).unwrap();
1948        seed.put(key("b"), value("v1")).unwrap();
1949        manager.commit(seed).unwrap();
1950
1951        let mut txn1 = manager.begin(TxnMode::ReadWrite).unwrap();
1952
1953        let mut txn2 = manager.begin(TxnMode::ReadWrite).unwrap();
1954        txn2.put(key("ba"), value("v2")).unwrap();
1955        manager.commit(txn2).unwrap();
1956
1957        let results: Vec<_> = txn1.scan_range(b"b", b"c").unwrap().collect();
1958        assert_eq!(results, vec![(key("b"), value("v1"))]);
1959    }
1960
1961    #[test]
1962    fn scan_range_records_reads_for_conflict_detection() {
1963        let store = MemoryKV::new();
1964        let manager = store.txn_manager();
1965
1966        let mut seed = manager.begin(TxnMode::ReadWrite).unwrap();
1967        seed.put(key("k1"), value("v1")).unwrap();
1968        manager.commit(seed).unwrap();
1969
1970        let mut t1 = manager.begin(TxnMode::ReadWrite).unwrap();
1971        let results: Vec<_> = t1.scan_range(b"k0", b"kz").unwrap().collect();
1972        assert_eq!(results, vec![(key("k1"), value("v1"))]);
1973        t1.put(key("k_new"), value("v_new")).unwrap();
1974
1975        let mut t2 = manager.begin(TxnMode::ReadWrite).unwrap();
1976        t2.put(key("k1"), value("v2")).unwrap();
1977        manager.commit(t2).unwrap();
1978
1979        let result = manager.commit(t1);
1980        assert!(matches!(result, Err(Error::TxnConflict)));
1981    }
1982
1983    #[test]
1984    fn owned_memory_transaction_merges_incremental_cursor_and_commits_once() {
1985        use crate::kv::OwnedSessionFactory;
1986
1987        let store = Arc::new(MemoryKV::new());
1988        let session = store
1989            .clone()
1990            .begin_owned_transaction(TxnMode::ReadWrite)
1991            .unwrap();
1992        let lease = session.acquire_lease().unwrap();
1993        lease
1994            .with_transaction(|transaction| {
1995                transaction.put(key("p:1"), value("one"))?;
1996                transaction.put(key("p:2"), value("two"))?;
1997                transaction.put(key("q:1"), value("other"))?;
1998                Ok(())
1999            })
2000            .unwrap();
2001        let mut cursor = lease
2002            .with_transaction(|transaction| transaction.scan_prefix(b"p:"))
2003            .unwrap();
2004        assert_eq!(
2005            cursor.next_entry().unwrap(),
2006            Some((key("p:1"), value("one")))
2007        );
2008        assert_eq!(
2009            cursor.next_entry().unwrap(),
2010            Some((key("p:2"), value("two")))
2011        );
2012        assert_eq!(cursor.next_entry().unwrap(), None);
2013        cursor.close().unwrap();
2014        drop(cursor);
2015        lease
2016            .finish(crate::txn::OwnedLeaseOutcome::Exhausted)
2017            .unwrap();
2018        session.commit().unwrap();
2019
2020        let read = store
2021            .clone()
2022            .begin_owned_read(crate::kv::OwnedReadOptions::default())
2023            .unwrap();
2024        let lease = read.acquire_lease().unwrap();
2025        assert_eq!(
2026            lease
2027                .with_transaction(|transaction| transaction.get(&key("p:2")))
2028                .unwrap(),
2029            Some(value("two"))
2030        );
2031        lease
2032            .finish(crate::txn::OwnedLeaseOutcome::Exhausted)
2033            .unwrap();
2034
2035        let range = store
2036            .clone()
2037            .begin_owned_read(crate::kv::OwnedReadOptions::default())
2038            .unwrap();
2039        let lease = range.acquire_lease().unwrap();
2040        let mut cursor = lease
2041            .with_transaction(|transaction| transaction.scan_range(b"p:1", b"q:"))
2042            .unwrap();
2043        assert_eq!(
2044            cursor.next_entry().unwrap(),
2045            Some((key("p:1"), value("one")))
2046        );
2047        assert_eq!(
2048            cursor.next_entry().unwrap(),
2049            Some((key("p:2"), value("two")))
2050        );
2051        assert_eq!(cursor.next_entry().unwrap(), None);
2052        drop(cursor);
2053        lease
2054            .finish(crate::txn::OwnedLeaseOutcome::Exhausted)
2055            .unwrap();
2056    }
2057
2058    #[test]
2059    fn owned_prefix_cursor_skips_keys_before_its_prefix() {
2060        use crate::kv::OwnedSessionFactory;
2061
2062        let store = Arc::new(MemoryKV::new());
2063        let writer = store
2064            .clone()
2065            .begin_owned_transaction(TxnMode::ReadWrite)
2066            .unwrap();
2067        let writer_lease = writer.acquire_lease().unwrap();
2068        writer_lease
2069            .with_transaction(|transaction| {
2070                transaction.put(key("catalog:before"), value("metadata"))?;
2071                transaction.put(key("row:1"), value("one"))?;
2072                Ok(())
2073            })
2074            .unwrap();
2075        writer_lease
2076            .finish(crate::txn::OwnedLeaseOutcome::Exhausted)
2077            .unwrap();
2078        writer.commit().unwrap();
2079
2080        let reader = store.clone().begin_owned_read(Default::default()).unwrap();
2081        let reader_lease = reader.acquire_lease().unwrap();
2082        let mut cursor = reader_lease
2083            .with_transaction(|transaction| transaction.scan_prefix(b"row:"))
2084            .unwrap();
2085        assert_eq!(
2086            cursor.next_entry().unwrap(),
2087            Some((key("row:1"), value("one")))
2088        );
2089        assert_eq!(cursor.next_entry().unwrap(), None);
2090        cursor.close().unwrap();
2091        drop(cursor);
2092        reader_lease
2093            .finish(crate::txn::OwnedLeaseOutcome::Exhausted)
2094            .unwrap();
2095    }
2096
2097    #[test]
2098    fn any_kv_memory_dispatches_to_owned_memory_session_without_borrowed_transaction() {
2099        use crate::kv::{AnyKV, OwnedSessionFactory};
2100
2101        let store = Arc::new(AnyKV::Memory(MemoryKV::new()));
2102        let session = store
2103            .clone()
2104            .begin_owned_transaction(TxnMode::ReadWrite)
2105            .unwrap();
2106        let lease = session.acquire_lease().unwrap();
2107        lease
2108            .with_transaction(|transaction| transaction.put(key("owned"), value("value")))
2109            .unwrap();
2110        lease
2111            .finish(crate::txn::OwnedLeaseOutcome::Exhausted)
2112            .unwrap();
2113        session.commit().unwrap();
2114
2115        let read = store
2116            .clone()
2117            .begin_owned_read(crate::kv::OwnedReadOptions::default())
2118            .unwrap();
2119        let lease = read.acquire_lease().unwrap();
2120        assert_eq!(
2121            lease
2122                .with_transaction(|transaction| transaction.get(&key("owned")))
2123                .unwrap(),
2124            Some(value("value"))
2125        );
2126        lease
2127            .finish(crate::txn::OwnedLeaseOutcome::Exhausted)
2128            .unwrap();
2129    }
2130
2131    #[test]
2132    fn memory_stats_tracks_put_and_delete() {
2133        let store = MemoryKV::new();
2134        let manager = store.txn_manager();
2135
2136        let stats = manager.memory_stats();
2137        assert_eq!(stats.total_bytes, 0);
2138        assert_eq!(stats.kv_bytes, 0);
2139        assert_eq!(stats.index_bytes, 0);
2140
2141        // Insert a value and commit.
2142        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
2143        txn.put(key("a"), value("1234")).unwrap(); // key=1, value=4 => 5 bytes
2144        manager.commit(txn).unwrap();
2145
2146        let stats = manager.memory_stats();
2147        assert_eq!(stats.total_bytes, 5);
2148        assert_eq!(stats.kv_bytes, 5);
2149        assert_eq!(stats.index_bytes, 0);
2150
2151        // Delete and ensure usage returns to zero.
2152        let mut txn = manager.begin(TxnMode::ReadWrite).unwrap();
2153        txn.delete(key("a")).unwrap();
2154        manager.commit(txn).unwrap();
2155
2156        let stats = manager.memory_stats();
2157        assert_eq!(stats.total_bytes, 0);
2158        assert_eq!(stats.kv_bytes, 0);
2159    }
2160
2161    #[test]
2162    fn memory_limit_error_does_not_break_reads() {
2163        let store = MemoryKV::new_with_limit(Some(10));
2164        let manager = store.txn_manager();
2165
2166        // First insert within limit: key(2) + value(4) = 6.
2167        let mut txn = manager.begin_internal(TxnMode::ReadWrite).unwrap();
2168        txn.put(key("k1"), value("vvvv")).unwrap();
2169        manager.commit(txn).unwrap();
2170
2171        // Next insert would exceed limit: key(2) + value(6) + existing(6) -> 14 > 10.
2172        let mut txn2 = manager.begin_internal(TxnMode::ReadWrite).unwrap();
2173        txn2.put(key("k2"), value("vvvvvv")).unwrap();
2174        let result = manager.commit(txn2);
2175        assert!(matches!(result, Err(Error::MemoryLimitExceeded { .. })));
2176
2177        // Read still works and existing data intact.
2178        let mut read_txn = manager.begin_internal(TxnMode::ReadOnly).unwrap();
2179        let got = read_txn.get(&key("k1")).unwrap();
2180        assert_eq!(got, Some(value("vvvv")));
2181
2182        // Memory usage stays at the previous successful commit.
2183        let stats = manager.memory_stats();
2184        assert_eq!(stats.total_bytes, 6);
2185    }
2186
2187    struct VecWriter(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
2188
2189    impl std::io::Write for VecWriter {
2190        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2191            let mut guard = self.0.lock().unwrap();
2192            guard.extend_from_slice(buf);
2193            Ok(buf.len())
2194        }
2195
2196        fn flush(&mut self) -> std::io::Result<()> {
2197            Ok(())
2198        }
2199    }
2200
2201    #[test]
2202    fn compaction_skips_when_over_limit_and_logs_warning() {
2203        let store = MemoryKV::new_with_limit(Some(12));
2204        let manager = store.txn_manager();
2205
2206        // Populate data to track current memory: key(2)+val(6)=8 bytes.
2207        let mut txn = manager.begin_internal(TxnMode::ReadWrite).unwrap();
2208        txn.put(key("k1"), value("123456")).unwrap();
2209        manager.commit(txn).unwrap();
2210
2211        // Prepare log capture.
2212        let buffer = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
2213        let make_writer = {
2214            let buf = buffer.clone();
2215            move || VecWriter(buf.clone())
2216        };
2217        let subscriber = tracing_subscriber::fmt()
2218            .with_max_level(Level::WARN)
2219            .with_writer(make_writer)
2220            .without_time()
2221            .finish();
2222        let _guard = tracing::subscriber::set_default(subscriber);
2223
2224        // input=2 (assume one entry), output=10 => projected 8-2+10=16 > 12 -> skip.
2225        let ran = manager.compact_with_limit(2, 10, || Ok(())).unwrap();
2226        assert!(!ran);
2227
2228        // Memory usage unchanged.
2229        assert_eq!(manager.memory_stats().total_bytes, 8);
2230
2231        // Verify warning was logged.
2232        let log = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
2233        assert!(
2234            log.contains("compaction skipped due to memory limit"),
2235            "expected warning log, got: {}",
2236            log
2237        );
2238    }
2239}