Skip to main content

uqa_storage/mvcc/
memory.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! In-memory reference owner for conditional commits and pinned record reads.
8
9use std::borrow::Borrow;
10use std::collections::BTreeMap;
11use std::sync::Arc;
12
13use parking_lot::Mutex;
14use uqa_core::memory::{BudgetedVec, MemoryBudget, MemoryReservation};
15
16use crate::read_control::StorageReadControl;
17
18use super::key::RecordKey;
19use super::{
20    CommitSequence, CommittedRecordSnapshot, PreparedRecordCommit, RecordHistory, RecordVersion,
21    RecordWrite, ScannedRecord, SharedRecordValue, VersionResult,
22};
23
24type History = RecordHistory<SharedRecordValue>;
25
26struct RecordEntry {
27    history: History,
28    _memory: MemoryReservation,
29}
30
31#[derive(Default)]
32struct State {
33    sequence: CommitSequence,
34    records: BTreeMap<RecordKey, RecordEntry>,
35    snapshots: BTreeMap<CommitSequence, usize>,
36}
37
38struct Database {
39    state: Mutex<State>,
40    memory: MemoryBudget,
41}
42
43/// Reference storage for the record contract, without physical durability.
44///
45/// Snapshot admission, conditional validation and publication share one mutex; no lock is retained between calls. SQL execution and transaction-local overlays are deliberately outside this committed-record owner.
46#[derive(Clone)]
47pub struct MemoryVersionStore {
48    database: Arc<Database>,
49}
50
51impl MemoryVersionStore {
52    pub fn new(memory: &MemoryBudget) -> Self {
53        Self {
54            database: Arc::new(Database {
55                state: Mutex::new(State::default()),
56                memory: memory.clone(),
57            }),
58        }
59    }
60
61    pub fn snapshot(&self) -> VersionResult<MemoryRecordSnapshot> {
62        let memory = self
63            .database
64            .memory
65            .reserve(std::mem::size_of::<MemoryRecordSnapshot>())?;
66        let mut state = self.database.state.lock();
67        let sequence = state.sequence;
68        let count = state.snapshots.entry(sequence).or_default();
69        *count = count
70            .checked_add(1)
71            .ok_or(uqa_core::memory::MemoryError::SizeOverflow)?;
72        Ok(MemoryRecordSnapshot {
73            database: Arc::clone(&self.database),
74            sequence,
75            _memory: memory,
76        })
77    }
78
79    /// Validate every original revision before publishing any record.
80    ///
81    /// Values and replacement histories are prepared privately. Failed validation, allocation or cancellation preserves all published records.
82    pub fn commit(
83        &self,
84        writes: &[RecordWrite<'_>],
85        control: &StorageReadControl,
86    ) -> VersionResult<CommitSequence> {
87        let preparation = StorageReadControl::new(&self.database.memory, control.cancellation());
88        let prepared = PreparedRecordCommit::new(writes, &preparation)?;
89        self.commit_prepared(&prepared, control)
90    }
91
92    pub fn commit_prepared(
93        &self,
94        commit: &PreparedRecordCommit,
95        control: &StorageReadControl,
96    ) -> VersionResult<CommitSequence> {
97        control.cancellation().check()?;
98        let values = self.retain_values(commit, control)?;
99        let mut state = self.database.state.lock();
100        commit.validate_snapshot(state.sequence)?;
101        let writes = commit.records();
102        let validate = || {
103            commit.validate(control.cancellation(), |key| {
104                Ok(state
105                    .records
106                    .get(key)
107                    .and_then(|entry| entry.history.head())
108                    .map(RecordVersion::sequence))
109            })
110        };
111        if writes.is_empty() {
112            validate()?;
113            return Ok(state.sequence);
114        }
115        let sequence = state.sequence.successor()?;
116        validate()?;
117        let mut prepared = BudgetedVec::new(&self.database.memory);
118        prepared.reserve(writes.len())?;
119        for (write, value) in writes.iter().zip(values.iter()) {
120            control.cancellation().check()?;
121            let key = RecordKey::new(write.key(), &self.database.memory)?;
122            let value = value.clone();
123            let history = if let Some(entry) = state.records.get(write.key()) {
124                entry.history.fork_appending(sequence, value)?
125            } else {
126                let mut history = History::new(&self.database.memory);
127                history.append(sequence, value)?;
128                history
129            };
130            let memory = self
131                .database
132                .memory
133                .reserve(std::mem::size_of::<(RecordKey, RecordEntry)>())?;
134            prepared.push((
135                key,
136                RecordEntry {
137                    history,
138                    _memory: memory,
139                },
140            ))?;
141        }
142        control.cancellation().check()?;
143        // Publication is infallible after every payload and history reservation
144        // succeeds. Tree-node bookkeeping follows the collection allocator.
145        while let Some((key, entry)) = prepared.pop() {
146            state.records.insert(key, entry);
147        }
148        state.sequence = sequence;
149        Ok(sequence)
150    }
151
152    fn retain_values(
153        &self,
154        commit: &PreparedRecordCommit,
155        control: &StorageReadControl,
156    ) -> VersionResult<BudgetedVec<Option<SharedRecordValue>>> {
157        let mut values = BudgetedVec::new(&self.database.memory);
158        values.reserve(commit.records().len())?;
159        for write in commit.records() {
160            control.cancellation().check()?;
161            let mut value = write.shared_value();
162            if let Some(source) = value
163                .as_ref()
164                .filter(|value| !value.budget().shares_allowance(&self.database.memory))
165            {
166                let mut owned = BudgetedVec::new(&self.database.memory);
167                owned.reserve(source.len())?;
168                for chunk in source.chunks(65536) {
169                    control.cancellation().check()?;
170                    owned.extend_from_slice(chunk)?;
171                }
172                value = Some(Arc::new(owned));
173            }
174            values.push(value)?;
175        }
176        Ok(values)
177    }
178
179    /// Reclaim histories under the same gate that admits new snapshots.
180    pub fn reclaim(&self) -> VersionResult<usize> {
181        let mut state = self.database.state.lock();
182        let horizon = state
183            .snapshots
184            .first_key_value()
185            .map_or(state.sequence, |(sequence, _)| *sequence);
186        state.records.values_mut().try_fold(0, |removed, entry| {
187            Ok(removed + entry.history.reclaim_before(horizon)?)
188        })
189    }
190}
191
192/// One pinned committed sequence. Dropping the last owner releases its lease.
193pub struct MemoryRecordSnapshot {
194    database: Arc<Database>,
195    sequence: CommitSequence,
196    _memory: MemoryReservation,
197}
198
199impl MemoryRecordSnapshot {
200    pub fn sequence(&self) -> CommitSequence {
201        self.sequence
202    }
203
204    /// Return a revision even if its payload is a tombstone.
205    pub fn get(&self, key: &[u8]) -> Option<RecordVersion<SharedRecordValue>> {
206        self.database
207            .state
208            .lock()
209            .records
210            .get(key)
211            .and_then(|entry| entry.history.visible_at(self.sequence))
212            .cloned()
213    }
214
215    /// Read a bounded ordered page, including tombstones for revision checks.
216    pub fn scan(
217        &self,
218        prefix: &[u8],
219        after: Option<&[u8]>,
220        limit: usize,
221        control: &StorageReadControl,
222    ) -> VersionResult<BudgetedVec<ScannedRecord>> {
223        control.cancellation().check()?;
224        let mut result = BudgetedVec::new(control.memory());
225        if limit == 0 {
226            return Ok(result);
227        }
228        let state = self.database.state.lock();
229        let start = after.filter(|after| *after >= prefix).unwrap_or(prefix);
230        for (key, entry) in state
231            .records
232            .range::<[u8], _>((std::ops::Bound::Included(start), std::ops::Bound::Unbounded))
233        {
234            control.cancellation().check()?;
235            let bytes: &[u8] = key.borrow();
236            if !bytes.starts_with(prefix) {
237                break;
238            }
239            if after.is_some_and(|after| bytes <= after) {
240                continue;
241            }
242            let Some(version) = entry.history.visible_at(self.sequence) else {
243                continue;
244            };
245            result.push(ScannedRecord {
246                key: copy_bytes(bytes, control.memory())?,
247                version: version.clone(),
248            })?;
249            if result.len() == limit {
250                break;
251            }
252        }
253        Ok(result)
254    }
255}
256
257impl CommittedRecordSnapshot for MemoryRecordSnapshot {
258    fn sequence(&self) -> CommitSequence {
259        Self::sequence(self)
260    }
261
262    fn get(
263        &self,
264        key: &[u8],
265        control: &StorageReadControl,
266    ) -> VersionResult<Option<RecordVersion<SharedRecordValue>>> {
267        control.cancellation().check()?;
268        Ok(Self::get(self, key))
269    }
270
271    fn scan(
272        &self,
273        prefix: &[u8],
274        after: Option<&[u8]>,
275        limit: usize,
276        control: &StorageReadControl,
277    ) -> VersionResult<BudgetedVec<ScannedRecord>> {
278        Self::scan(self, prefix, after, limit, control)
279    }
280
281    fn visit_prefix(
282        &self,
283        prefix: &[u8],
284        after: Option<&[u8]>,
285        limit: usize,
286        control: &StorageReadControl,
287        visit: &mut super::RecordScanVisitor<'_>,
288    ) -> VersionResult<()> {
289        control.cancellation().check()?;
290        if limit == 0 {
291            return Ok(());
292        }
293        let state = self.database.state.lock();
294        let start = after.filter(|after| *after >= prefix).unwrap_or(prefix);
295        let mut count = 0;
296        for (key, history) in state
297            .records
298            .range::<[u8], _>((std::ops::Bound::Included(start), std::ops::Bound::Unbounded))
299        {
300            control.cancellation().check()?;
301            let key = key.bytes();
302            if !key.starts_with(prefix) {
303                break;
304            }
305            if after.is_some_and(|after| key <= after) {
306                continue;
307            }
308            if let Some(record) = history.history.visible_at(self.sequence) {
309                let more = visit(
310                    key,
311                    super::BorrowedRecord {
312                        revision: Some(record.sequence()),
313                        value: record.value().map(|value| &***value),
314                    },
315                )?;
316                control.cancellation().check()?;
317                count += 1;
318                if !more || count == limit {
319                    break;
320                }
321            }
322        }
323        Ok(())
324    }
325}
326
327impl Drop for MemoryRecordSnapshot {
328    fn drop(&mut self) {
329        let mut state = self.database.state.lock();
330        let count = state
331            .snapshots
332            .get_mut(&self.sequence)
333            .expect("snapshot owns its lease");
334        *count -= 1;
335        if *count == 0 {
336            state.snapshots.remove(&self.sequence);
337        }
338    }
339}
340
341fn copy_bytes(value: &[u8], memory: &MemoryBudget) -> VersionResult<BudgetedVec<u8>> {
342    let mut owned = BudgetedVec::new(memory);
343    owned.extend_from_slice(value)?;
344    Ok(owned)
345}