Skip to main content

aranya_runtime/storage/linear/
mod.rs

1//! Persistant linear storage implemenatation.
2//!
3//! `LinearStorage` is a graph storage implementation backed by a file-like byte
4//! storage interface. This is designed to be usable across many environments
5//! with minimal assumptions on the underlying storage.
6//!
7//! # Layout
8//!
9//! `[x]` is page aligned.
10//!
11//! ```text
12//! // Control section
13//! [Base] [Root] [Root]
14//! // Data section
15//! [Segment or FactIndex]
16//! |
17//! V
18//! ```
19//!
20//! The `LinearStorage` will exclusively modify the control section. The data
21//! section is append-only but can be read concurrently. If written data is not
22//! committed, it may be overwritten and will become unreachable by intended
23//! means.
24
25pub mod libc;
26
27#[cfg(feature = "testing")]
28pub mod testing;
29
30use alloc::{boxed::Box, collections::BTreeMap, string::String, vec, vec::Vec};
31use core::ops::Bound;
32
33use buggy::{Bug, BugExt as _, bug};
34use serde::{Deserialize, Serialize};
35use vec1::Vec1;
36
37use crate::{
38    Address, Bytes, Checkpoint, CmdId, Command, CommandExt as _, Fact, FactIndex, FactPerspective,
39    GraphId, HeadSet, HeadSetOffset, Keys, LocatedAddress, Location, MaxCut, Perspective, PolicyId,
40    Prior, Priority, Query, QueryMut, Revertable, Segment, SegmentIndex, Storage, StorageError,
41    StorageProvider,
42};
43
44pub mod io;
45pub use io::*;
46
47/// Maximum depth of fact indices before compaction.
48///
49/// A lower value will speed up search queries but require more compaction,
50/// slowing down fact index creation and using more storage space.
51///
52/// In the future, this may be configurable at runtime or dynamic based on
53/// heuristics such as fact density.
54///
55/// 16 is our initial guess for balance.
56///
57/// This must be at least 2.
58const MAX_FACT_INDEX_DEPTH: u64 = 16;
59
60pub struct LinearStorageProvider<FM: IoManager> {
61    manager: FM,
62    storage: BTreeMap<GraphId, LinearStorage<FM::Writer>>,
63}
64
65pub struct LinearStorage<W> {
66    writer: W,
67    /// In-memory copy of the committed head set, kept in sync on every commit.
68    /// Lets [`get_heads`](Storage::get_heads) hand out a borrow without
69    /// re-reading or deserializing the set on hot paths.
70    cached_heads: HeadSet,
71}
72
73#[derive(Debug)]
74pub struct LinearSegment<R> {
75    repr: SegmentRepr,
76    reader: R,
77}
78
79#[derive(Debug, Serialize, Deserialize)]
80struct SegmentRepr {
81    /// Self offset in file.
82    offset: SegmentIndex,
83    prior: Prior<Location>,
84    parents: Prior<Address>,
85    policy: PolicyId,
86    /// Offset in file to associated fact index.
87    facts: u64,
88    /// Prior fact offset used to reconstruct facts within segment.
89    prior_facts: Option<u64>,
90    commands: Vec1<CommandData>,
91    max_cut: MaxCut,
92    skip_list: Vec<Location>,
93}
94
95#[derive(Debug, Serialize, Deserialize)]
96struct CommandData {
97    id: CmdId,
98    priority: Priority,
99    policy: Option<Bytes>,
100    data: Bytes,
101    updates: Vec<Update>,
102}
103
104pub struct LinearCommand<'a> {
105    id: &'a CmdId,
106    parent: Prior<Address>,
107    priority: Priority,
108    policy: Option<&'a [u8]>,
109    data: &'a [u8],
110}
111
112type Update = (String, Keys, Option<Bytes>);
113type FactMap = BTreeMap<Keys, Option<Box<[u8]>>>;
114type NamedFactMap = BTreeMap<String, FactMap>;
115
116#[derive(Debug)]
117pub struct LinearFactIndex<R> {
118    repr: FactIndexRepr,
119    reader: R,
120}
121
122#[derive(Debug, Serialize, Deserialize)]
123struct FactIndexRepr {
124    /// Self offset in file.
125    offset: u64,
126    /// Offset of prior fact index.
127    prior: Option<u64>,
128    /// Depth of this fact index.
129    ///
130    /// `prior.depth + 1`, or just `1` if no prior
131    depth: u64,
132    /// Facts in sorted order
133    facts: NamedFactMap,
134}
135
136#[derive(Debug)]
137pub struct LinearPerspective<R> {
138    prior: Prior<Location>,
139    parents: Prior<Address>,
140    policy: PolicyId,
141    facts: LinearFactPerspective<R>,
142    commands: Vec<CommandData>,
143    current_updates: Vec<Update>,
144    max_cut: MaxCut,
145    last_common_ancestor: Option<Location>,
146}
147
148impl<R> LinearPerspective<R> {
149    fn new(
150        prior: Prior<Location>,
151        parents: Prior<Address>,
152        policy: PolicyId,
153        prior_facts: FactPerspectivePrior<R>,
154        max_cut: MaxCut,
155        last_common_ancestor: Option<Location>,
156    ) -> Self {
157        Self {
158            prior,
159            parents,
160            policy,
161            facts: LinearFactPerspective::new(prior_facts),
162            commands: Vec::new(),
163            current_updates: Vec::new(),
164            max_cut,
165            last_common_ancestor,
166        }
167    }
168}
169
170#[derive(Debug)]
171pub struct LinearFactPerspective<R> {
172    map: BTreeMap<String, BTreeMap<Keys, Option<Bytes>>>,
173    prior: FactPerspectivePrior<R>,
174}
175
176impl<R> LinearFactPerspective<R> {
177    fn new(prior: FactPerspectivePrior<R>) -> Self {
178        Self {
179            map: BTreeMap::new(),
180            prior,
181        }
182    }
183}
184
185#[derive(Debug)]
186enum FactPerspectivePrior<R> {
187    None,
188    FactPerspective(Box<LinearFactPerspective<R>>),
189    FactIndex { offset: u64, reader: R },
190}
191
192impl<R> FactPerspectivePrior<R> {
193    fn is_none(&self) -> bool {
194        matches!(self, Self::None)
195    }
196}
197
198impl<FM: IoManager + Default> Default for LinearStorageProvider<FM> {
199    fn default() -> Self {
200        Self {
201            manager: FM::default(),
202            storage: BTreeMap::new(),
203        }
204    }
205}
206
207impl<FM: IoManager> LinearStorageProvider<FM> {
208    pub fn new(manager: FM) -> Self {
209        Self {
210            manager,
211            storage: BTreeMap::new(),
212        }
213    }
214}
215
216impl<FM: IoManager> StorageProvider for LinearStorageProvider<FM> {
217    type Perspective = LinearPerspective<<FM::Writer as Write>::ReadOnly>;
218    type Segment = LinearSegment<<FM::Writer as Write>::ReadOnly>;
219    type Storage = LinearStorage<FM::Writer>;
220
221    fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective {
222        LinearPerspective::new(
223            Prior::None,
224            Prior::None,
225            policy_id,
226            FactPerspectivePrior::None,
227            MaxCut::new(0),
228            None,
229        )
230    }
231
232    fn new_storage(
233        &mut self,
234        init: Self::Perspective,
235    ) -> Result<(GraphId, &mut Self::Storage), StorageError> {
236        use alloc::collections::btree_map::Entry;
237
238        if init.commands.is_empty() {
239            return Err(StorageError::EmptyPerspective);
240        }
241        let graph_id = GraphId::transmute(init.commands[0].id);
242        let Entry::Vacant(entry) = self.storage.entry(graph_id) else {
243            return Err(StorageError::StorageExists);
244        };
245
246        let file = self.manager.create(graph_id)?;
247        Ok((graph_id, entry.insert(LinearStorage::create(file, init)?)))
248    }
249
250    fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError> {
251        use alloc::collections::btree_map::Entry;
252
253        let entry = match self.storage.entry(graph) {
254            Entry::Vacant(v) => v,
255            Entry::Occupied(o) => return Ok(o.into_mut()),
256        };
257
258        let file = self
259            .manager
260            .open(graph)?
261            .ok_or(StorageError::NoSuchStorage)?;
262        Ok(entry.insert(LinearStorage::open(file)?))
263    }
264
265    fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError> {
266        self.manager.remove(graph)?;
267
268        self.storage
269            .remove(&graph)
270            .ok_or(StorageError::NoSuchStorage)?;
271
272        Ok(())
273    }
274
275    fn list_graph_ids(
276        &mut self,
277    ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError> {
278        self.manager.list()
279    }
280}
281
282/// Maximum segment-walk distance for skip-list construction. Below this
283/// threshold the segments are cheap enough to walk one-by-one, so neither
284/// the rich-anchor probe nor a freshly built skip list pay for themselves.
285const MIN_SKIP_GAP: u64 = 10;
286
287/// Skip-list target boundaries for a segment of length `n`: `n/2`, `3n/4`,
288/// `7n/8`, ..., halving the remaining gap each step. Continues until the
289/// gap from the final boundary to `n` is ≤ [`MIN_SKIP_GAP`], so the walk
290/// from head to the first skip entry never exceeds the cheap-walk
291/// threshold. Returned ascending; callers walk backwards and pop
292/// highest-first. Empty when `n < 2`.
293fn skip_target_boundaries(n: u64) -> Result<Vec<MaxCut>, StorageError> {
294    let mut targets = vec![];
295    let mut boundary = n / 2;
296    while boundary > 0 {
297        targets.push(MaxCut::new(boundary));
298        let gap = n
299            .checked_sub(boundary)
300            .assume("boundary < n by loop invariant")?;
301        if gap <= MIN_SKIP_GAP {
302            break;
303        }
304        boundary = boundary
305            .checked_add(gap / 2)
306            .assume("boundary + gap/2 <= n <= u64::MAX")?;
307    }
308    Ok(targets)
309}
310
311impl<W: Write> LinearStorage<W> {
312    fn create(mut writer: W, init: LinearPerspective<W::ReadOnly>) -> Result<Self, StorageError> {
313        assert!(matches!(init.prior, Prior::None));
314        assert!(matches!(init.parents, Prior::None));
315        assert!(matches!(init.facts.prior, FactPerspectivePrior::None));
316
317        let mut map = init.facts.map;
318        map.retain(|_, kv| !kv.is_empty());
319
320        let facts = writer
321            .append(|offset| FactIndexRepr {
322                offset,
323                prior: None,
324                depth: 1,
325                facts: map,
326            })?
327            .offset;
328
329        let commands = init
330            .commands
331            .try_into()
332            .map_err(|_| StorageError::EmptyPerspective)?;
333        let segment = writer.append(|offset| SegmentRepr {
334            offset: SegmentIndex::new(offset),
335            prior: Prior::None,
336            parents: Prior::None,
337            policy: init.policy,
338            facts,
339            prior_facts: None,
340            commands,
341            max_cut: MaxCut::new(0),
342            skip_list: vec![],
343        })?;
344
345        let max_cut = segment
346            .max_cut
347            .checked_add(
348                segment
349                    .commands
350                    .len()
351                    .checked_sub(1)
352                    .assume("vec1 length >= 1")? as u64,
353            )
354            .assume("valid max cut")?;
355        let head = LocatedAddress {
356            id: segment.commands.last().id,
357            segment: segment.offset,
358            max_cut,
359        };
360
361        // Seed both the one-element head set and the fact cache (the init
362        // segment's fact index, stored at `facts`).
363        let cached_heads = HeadSet::single(head);
364        writer.commit(&cached_heads, FactCacheOffset::new(facts))?;
365
366        let storage = Self {
367            writer,
368            cached_heads,
369        };
370
371        Ok(storage)
372    }
373
374    fn open(writer: W) -> Result<Self, StorageError> {
375        let cached_heads = writer.heads()?;
376        Ok(Self {
377            writer,
378            cached_heads,
379        })
380    }
381
382    fn compact(&mut self, mut repr: FactIndexRepr) -> Result<FactIndexRepr, StorageError> {
383        let mut map = NamedFactMap::new();
384        let reader = self.writer.readonly();
385        loop {
386            for (name, kv) in repr.facts {
387                let sub = map.entry(name).or_default();
388                for (k, v) in kv {
389                    sub.entry(k).or_insert(v);
390                }
391            }
392            let Some(offset) = repr.prior else { break };
393            repr = reader.fetch(offset)?;
394        }
395
396        // Since there's no prior, we can remove tombstones
397        map.retain(|_, kv| {
398            kv.retain(|_, v| v.is_some());
399            !kv.is_empty()
400        });
401
402        Ok(self
403            .write_facts(LinearFactPerspective {
404                map,
405                prior: FactPerspectivePrior::None,
406            })?
407            .repr)
408    }
409
410    /// Whether an ancestor within [`MIN_SKIP_GAP`] segments of `start` already
411    /// carries a rich skip list (`len > 1`). The walk crosses merges via the
412    /// LCA recorded as the sole entry in a merge segment's LCA-only skip list,
413    /// so a rich anchor past a merge is still reachable.
414    fn has_nearby_rich_anchor(&self, start: Location) -> Result<bool, StorageError> {
415        let mut check = start;
416        for _ in 0..MIN_SKIP_GAP {
417            let seg = self.get_segment(check)?;
418            if seg.skip_list().len() > 1 {
419                return Ok(true);
420            }
421            match seg.prior() {
422                Prior::Single(p) => check = p,
423                Prior::Merge(_, _) => {
424                    check = seg
425                        .skip_list()
426                        .last()
427                        .copied()
428                        .assume("merge skip list must end with LCA")?;
429                }
430                Prior::None => return Ok(false),
431            }
432        }
433        Ok(false)
434    }
435
436    /// Build the skip list for a new segment with the given `prior`,
437    /// `last_common_ancestor` (required for merges), and length `n`.
438    ///
439    /// Returns:
440    /// - empty for `Prior::None`,
441    /// - `[lca]` (or empty for non-merges) when a nearby ancestor already
442    ///   has a rich skip list or `n < MIN_SKIP_GAP`,
443    /// - otherwise, a list of skip targets at `n/2, 3n/4, 7n/8, ...` plus
444    ///   the LCA for merges. See [`skip_target_boundaries`].
445    fn build_skip_list(
446        &self,
447        prior: Prior<Location>,
448        last_common_ancestor: Option<Location>,
449        n: u64,
450    ) -> Result<Vec<Location>, StorageError> {
451        let (walk_start, lca) = match prior {
452            Prior::None => return Ok(vec![]),
453            Prior::Merge(_, _) => {
454                let lca = last_common_ancestor.assume("lca must exist")?;
455                (lca, Some(lca))
456            }
457            Prior::Single(l) => (l, None),
458        };
459
460        if self.has_nearby_rich_anchor(walk_start)? || n < MIN_SKIP_GAP {
461            return Ok(lca.into_iter().collect());
462        }
463
464        let targets = skip_target_boundaries(n)?;
465        let mut skips = self.walk_collecting_skips(walk_start, targets)?;
466
467        // Always include the LCA for merge segments.
468        if let Some(lca) = lca
469            && !skips.contains(&lca)
470        {
471            skips.push(lca);
472        }
473
474        skips.sort_by_key(|loc| loc.max_cut);
475        skips.dedup();
476        Ok(skips)
477    }
478
479    /// Walk backwards from `start`, recording the `first_location` of each
480    /// segment as it crosses a target in `targets` (ascending; consumed
481    /// highest-first via `pop`). At each segment, jump along the smallest
482    /// available skip entry that still stays at or above the next target;
483    /// otherwise step to the parent. Stops when targets are exhausted or
484    /// no further progress toward them is possible.
485    fn walk_collecting_skips(
486        &self,
487        start: Location,
488        mut targets: Vec<MaxCut>,
489    ) -> Result<Vec<Location>, StorageError> {
490        let mut skips = vec![];
491        let mut current = start;
492
493        loop {
494            let seg = self.get_segment(current)?;
495            let seg_min = seg.shortest_max_cut();
496
497            // Record any targets we've reached or passed.
498            while let Some(&t) = targets.last() {
499                if t >= seg_min {
500                    skips.push(seg.first_location());
501                    targets.pop();
502                } else {
503                    break;
504                }
505            }
506
507            let Some(&next_target) = targets.last() else {
508                break;
509            };
510
511            // Smallest skip entry at or above next_target (and below
512            // current), i.e. the tightest jump that still makes progress.
513            let best = seg
514                .skip_list()
515                .iter()
516                .copied()
517                .filter(|s| s.max_cut >= next_target && s.max_cut < current.max_cut)
518                .min_by_key(|s| s.max_cut);
519            if let Some(skip) = best {
520                current = skip;
521                continue;
522            }
523
524            match seg.prior() {
525                Prior::Single(p) if p.max_cut >= next_target => current = p,
526                _ => break,
527            }
528        }
529
530        Ok(skips)
531    }
532
533    /// Write a fact perspective out if non-empty, returning it with the prior fact offset to be stored in the segment.
534    fn write_facts_with_prior(
535        &mut self,
536        facts: <Self as Storage>::FactPerspective,
537    ) -> Result<(<Self as Storage>::FactIndex, Option<u64>), StorageError> {
538        let mut prior = match facts.prior {
539            FactPerspectivePrior::None => None,
540            FactPerspectivePrior::FactPerspective(prior) => {
541                let prior = self.write_facts(*prior)?;
542                if facts.map.is_empty() {
543                    let offset = prior.repr.offset;
544                    return Ok((prior, Some(offset)));
545                }
546                Some(prior.repr)
547            }
548            FactPerspectivePrior::FactIndex { offset, reader } => {
549                let repr: FactIndexRepr = reader.fetch(offset)?;
550                if facts.map.is_empty() {
551                    let offset = repr.offset;
552                    return Ok((LinearFactIndex { repr, reader }, Some(offset)));
553                }
554                Some(repr)
555            }
556        };
557
558        let depth = if let Some(mut p) = prior.take() {
559            if p.depth > MAX_FACT_INDEX_DEPTH - 1 {
560                p = self.compact(p)?;
561            }
562            prior.insert(p).depth
563        } else {
564            0
565        };
566
567        let depth = depth.checked_add(1).assume("depth won't overflow")?;
568
569        if depth > MAX_FACT_INDEX_DEPTH {
570            bug!("fact index too deep");
571        }
572
573        let prior_offset = prior.map(|p| p.offset);
574        let repr = self.writer.append(|offset| FactIndexRepr {
575            offset,
576            prior: prior_offset,
577            depth,
578            facts: facts.map,
579        })?;
580
581        Ok((
582            LinearFactIndex {
583                repr,
584                reader: self.writer.readonly(),
585            },
586            prior_offset,
587        ))
588    }
589}
590
591impl<F: Write> Storage for LinearStorage<F> {
592    type Perspective = LinearPerspective<F::ReadOnly>;
593    type FactPerspective = LinearFactPerspective<F::ReadOnly>;
594    type Segment = LinearSegment<F::ReadOnly>;
595    type FactIndex = LinearFactIndex<F::ReadOnly>;
596
597    fn get_linear_perspective(&self, parent: Location) -> Result<Self::Perspective, StorageError> {
598        let segment = self.get_segment(parent)?;
599        let command = segment
600            .get_command(parent)
601            .ok_or(StorageError::CommandOutOfBounds(parent))?;
602        let policy = segment.repr.policy;
603        let prior_facts: FactPerspectivePrior<F::ReadOnly> = if parent == segment.head_location()? {
604            FactPerspectivePrior::FactIndex {
605                offset: segment.repr.facts,
606                reader: self.writer.readonly(),
607            }
608        } else {
609            let prior = match segment.repr.prior_facts {
610                Some(offset) => FactPerspectivePrior::FactIndex {
611                    offset,
612                    reader: self.writer.readonly(),
613                },
614                None => FactPerspectivePrior::None,
615            };
616            let mut facts = LinearFactPerspective::new(prior);
617            for data in &segment.repr.commands[..=segment.repr.cmd_index(parent.max_cut)?] {
618                facts.apply_updates(&data.updates)?;
619            }
620            if facts.prior.is_none() {
621                facts.map.retain(|_, kv| !kv.is_empty());
622            }
623            if facts.map.is_empty() {
624                facts.prior
625            } else {
626                FactPerspectivePrior::FactPerspective(Box::new(facts))
627            }
628        };
629        let prior = Prior::Single(parent);
630
631        let perspective = LinearPerspective::new(
632            prior,
633            Prior::Single(command.address()?),
634            policy,
635            prior_facts,
636            command
637                .max_cut()?
638                .checked_add(1)
639                .assume("must not overflow")?,
640            None,
641        );
642
643        Ok(perspective)
644    }
645
646    fn get_fact_perspective(
647        &self,
648        location: Location,
649    ) -> Result<Self::FactPerspective, StorageError> {
650        let segment = self.get_segment(location)?;
651
652        // If at head of segment, or no facts in segment,
653        // we don't need to apply updates.
654        if location == segment.head_location()?
655            || segment
656                .repr
657                .commands
658                .iter()
659                .all(|cmd| cmd.updates.is_empty())
660        {
661            return Ok(LinearFactPerspective::new(
662                FactPerspectivePrior::FactIndex {
663                    offset: segment.repr.facts,
664                    reader: self.writer.readonly(),
665                },
666            ));
667        }
668
669        let prior = match segment.repr.prior_facts {
670            Some(offset) => FactPerspectivePrior::FactIndex {
671                offset,
672                reader: self.writer.readonly(),
673            },
674            None => FactPerspectivePrior::None,
675        };
676        let mut facts = LinearFactPerspective::new(prior);
677        for data in &segment.repr.commands[..=segment.repr.cmd_index(location.max_cut)?] {
678            facts.apply_updates(&data.updates)?;
679        }
680
681        Ok(facts)
682    }
683
684    fn new_merge_perspective(
685        &self,
686        left: Location,
687        right: Location,
688        last_common_ancestor: Location,
689        policy_id: PolicyId,
690        braid: Self::FactIndex,
691    ) -> Result<Self::Perspective, StorageError> {
692        // TODO(jdygert): ensure braid belongs to this storage.
693        // TODO(jdygert): ensure braid ends at given command?
694        let left_segment = self.get_segment(left)?;
695        let left_command = left_segment
696            .get_command(left)
697            .ok_or(StorageError::CommandOutOfBounds(left))?;
698        let right_segment = self.get_segment(right)?;
699        let right_command = right_segment
700            .get_command(right)
701            .ok_or(StorageError::CommandOutOfBounds(right))?;
702
703        let parent = Prior::Merge(left_command.address()?, right_command.address()?);
704
705        if policy_id != left_segment.policy() && policy_id != right_segment.policy() {
706            return Err(StorageError::PolicyMismatch);
707        }
708
709        let prior = Prior::Merge(left, right);
710
711        let perspective = LinearPerspective::new(
712            prior,
713            parent,
714            policy_id,
715            FactPerspectivePrior::FactIndex {
716                offset: braid.repr.offset,
717                reader: braid.reader,
718            },
719            left_command
720                .max_cut()?
721                .max(right_command.max_cut()?)
722                .checked_add(1)
723                .assume("must not overflow")?,
724            Some(last_common_ancestor),
725        );
726
727        Ok(perspective)
728    }
729
730    fn get_segment(&self, location: Location) -> Result<Self::Segment, StorageError> {
731        let reader = self.writer.readonly();
732        let repr = reader.fetch(location.segment.get())?;
733        let seg = LinearSegment { repr, reader };
734
735        Ok(seg)
736    }
737
738    fn get_heads(&self) -> Result<&HeadSet, StorageError> {
739        Ok(&self.cached_heads)
740    }
741
742    fn heads_offset(&self) -> Result<HeadSetOffset, StorageError> {
743        self.writer.heads_offset()
744    }
745
746    fn fact_cache(&self) -> Result<Self::FactIndex, StorageError> {
747        let offset = self.writer.fact_cache()?;
748        Ok(LinearFactIndex {
749            repr: self.writer.readonly().fetch(offset.get())?,
750            reader: self.writer.readonly(),
751        })
752    }
753
754    fn commit_heads(
755        &mut self,
756        heads: HeadSet,
757        fact_cache: Self::FactIndex,
758    ) -> Result<(), StorageError> {
759        self.writer
760            .commit(&heads, FactCacheOffset::new(fact_cache.repr.offset))?;
761        self.cached_heads = heads;
762        Ok(())
763    }
764
765    fn write(&mut self, perspective: Self::Perspective) -> Result<Self::Segment, StorageError> {
766        // TODO(jdygert): Validate prior?
767
768        let (facts, prior_facts) = self.write_facts_with_prior(perspective.facts)?;
769        let facts = facts.repr.offset;
770
771        let commands: Vec1<CommandData> = perspective
772            .commands
773            .try_into()
774            .map_err(|_| StorageError::EmptyPerspective)?;
775
776        let skip_list = self.build_skip_list(
777            perspective.prior,
778            perspective.last_common_ancestor,
779            perspective.max_cut.get(),
780        )?;
781
782        let repr = self.writer.append(|offset| SegmentRepr {
783            offset: SegmentIndex::new(offset),
784            prior: perspective.prior,
785            parents: perspective.parents,
786            policy: perspective.policy,
787            facts,
788            prior_facts,
789            commands,
790            max_cut: perspective.max_cut,
791            skip_list,
792        })?;
793
794        Ok(LinearSegment {
795            repr,
796            reader: self.writer.readonly(),
797        })
798    }
799
800    fn write_facts(
801        &mut self,
802        facts: Self::FactPerspective,
803    ) -> Result<Self::FactIndex, StorageError> {
804        self.write_facts_with_prior(facts)
805            .map(|(fact_index, _)| fact_index)
806    }
807}
808
809impl<R: Read> Segment for LinearSegment<R> {
810    type FactIndex = LinearFactIndex<R>;
811    type Command<'a>
812        = LinearCommand<'a>
813    where
814        R: 'a;
815
816    fn index(&self) -> SegmentIndex {
817        self.repr.offset
818    }
819
820    fn head_id(&self) -> CmdId {
821        self.repr.commands.last().id
822    }
823
824    fn first_location(&self) -> Location {
825        Location::new(self.repr.offset, self.repr.max_cut)
826    }
827
828    fn policy(&self) -> PolicyId {
829        self.repr.policy
830    }
831
832    fn prior(&self) -> Prior<Location> {
833        self.repr.prior
834    }
835
836    fn get_command(&self, location: Location) -> Option<Self::Command<'_>> {
837        if self.repr.offset != location.segment {
838            return None;
839        }
840        let cmd_idx = self.repr.cmd_index(location.max_cut).ok()?;
841        let data = self.repr.commands.get(cmd_idx)?;
842        let parent = if let Some(prev) = usize::checked_sub(cmd_idx, 1) {
843            if let Some(max_cut) = self.repr.max_cut.checked_add(prev as u64) {
844                Prior::Single(Address {
845                    id: self.repr.commands[prev].id,
846                    max_cut,
847                })
848            } else {
849                return None;
850            }
851        } else {
852            self.repr.parents
853        };
854        Some(LinearCommand {
855            id: &data.id,
856            parent,
857            priority: data.priority.clone(),
858            policy: data.policy.as_deref(),
859            data: &data.data,
860        })
861    }
862
863    fn facts(&self) -> Result<Self::FactIndex, StorageError> {
864        Ok(LinearFactIndex {
865            repr: self.reader.fetch(self.repr.facts)?,
866            reader: self.reader.clone(),
867        })
868    }
869
870    fn skip_list(&self) -> &[Location] {
871        &self.repr.skip_list
872    }
873
874    fn shortest_max_cut(&self) -> MaxCut {
875        self.repr.max_cut
876    }
877
878    fn longest_max_cut(&self) -> Result<MaxCut, StorageError> {
879        Ok(self
880            .repr
881            .max_cut
882            .checked_add(
883                self.repr
884                    .commands
885                    .len()
886                    .checked_sub(1)
887                    .assume("must not overflow")? as u64,
888            )
889            .assume("must not overflow")?)
890    }
891}
892
893impl SegmentRepr {
894    fn cmd_index(&self, max_cut: MaxCut) -> Result<usize, StorageError> {
895        max_cut
896            .distance_from(self.max_cut)
897            .and_then(|x| usize::try_from(x).ok())
898            .ok_or(StorageError::CommandOutOfBounds(Location::new(
899                self.offset,
900                max_cut,
901            )))
902    }
903}
904
905impl<R: Read> FactIndex for LinearFactIndex<R> {}
906
907#[cfg(all(test, feature = "graphviz"))]
908impl<R: Read> crate::storage::FactIndexExtra for LinearFactIndex<R> {
909    fn name(&self) -> String {
910        use alloc::string::ToString as _;
911        self.repr.offset.to_string()
912    }
913
914    fn prior(&self) -> Result<Option<Self>, StorageError> {
915        self.repr
916            .prior
917            .map(|p| {
918                let repr = self.reader.fetch(p)?;
919                Ok(Self {
920                    repr,
921                    reader: self.reader.clone(),
922                })
923            })
924            .transpose()
925    }
926}
927
928type MapIter = alloc::collections::btree_map::IntoIter<Keys, Option<Bytes>>;
929pub struct QueryIterator {
930    it: MapIter,
931}
932
933impl QueryIterator {
934    fn new(it: MapIter) -> Self {
935        Self { it }
936    }
937}
938
939impl Iterator for QueryIterator {
940    type Item = Result<Fact, StorageError>;
941    fn next(&mut self) -> Option<Self::Item> {
942        loop {
943            // filter out tombstones
944            if let (key, Some(value)) = self.it.next()? {
945                return Some(Ok(Fact { key, value }));
946            }
947        }
948    }
949}
950
951impl<R: Read> Query for LinearFactIndex<R> {
952    fn query(&self, name: &str, keys: &[Bytes]) -> Result<Option<Bytes>, StorageError> {
953        let mut prior = Some(&self.repr);
954        let mut slot; // Need to store deserialized value.
955        while let Some(facts) = prior {
956            if let Some(v) = facts.facts.get(name).and_then(|m| m.get(keys)) {
957                return Ok(v.clone());
958            }
959            slot = facts.prior.map(|p| self.reader.fetch(p)).transpose()?;
960            prior = slot.as_ref();
961        }
962        Ok(None)
963    }
964
965    type QueryIterator = QueryIterator;
966    fn query_prefix(&self, name: &str, prefix: &[Bytes]) -> Result<QueryIterator, StorageError> {
967        Ok(QueryIterator::new(
968            self.query_prefix_inner(name, prefix)?.into_iter(),
969        ))
970    }
971}
972
973impl<R: Read> LinearFactIndex<R> {
974    fn query_prefix_inner(&self, name: &str, prefix: &[Bytes]) -> Result<FactMap, StorageError> {
975        let mut matches = BTreeMap::new();
976        let mut prior = Some(&self.repr);
977        let mut slot; // Need to store deserialized value.
978        while let Some(facts) = prior {
979            if let Some(map) = facts.facts.get(name) {
980                for (k, v) in find_prefixes(map, prefix) {
981                    // don't override, if we've already found the fact (including deletions)
982                    if !matches.contains_key(k) {
983                        matches.insert(k.clone(), v.map(Into::into));
984                    }
985                }
986            }
987            slot = facts.prior.map(|p| self.reader.fetch(p)).transpose()?;
988            prior = slot.as_ref();
989        }
990        Ok(matches)
991    }
992}
993
994impl<R> LinearFactPerspective<R> {
995    fn clear(&mut self) {
996        self.map.clear();
997    }
998
999    fn apply_updates(&mut self, updates: &[Update]) -> Result<(), StorageError> {
1000        for (name, keys, value) in updates {
1001            if self.prior.is_none() {
1002                if let Some(value) = value {
1003                    self.map
1004                        .entry(name.clone())
1005                        .or_default()
1006                        .insert(keys.clone(), Some(value.clone()));
1007                } else if let Some(e) = self.map.get_mut(name) {
1008                    e.remove(keys);
1009                }
1010            } else {
1011                self.map
1012                    .entry(name.clone())
1013                    .or_default()
1014                    .insert(keys.clone(), value.clone());
1015            }
1016        }
1017        Ok(())
1018    }
1019}
1020
1021impl<R: Read> FactPerspective for LinearFactPerspective<R> {}
1022
1023impl<R: Read> Query for LinearFactPerspective<R> {
1024    fn query(&self, name: &str, keys: &[Bytes]) -> Result<Option<Bytes>, StorageError> {
1025        if let Some(wrapped) = self.map.get(name).and_then(|m| m.get(keys)) {
1026            return Ok(wrapped.as_deref().map(Bytes::from));
1027        }
1028        match &self.prior {
1029            FactPerspectivePrior::None => Ok(None),
1030            FactPerspectivePrior::FactPerspective(prior) => prior.query(name, keys),
1031            FactPerspectivePrior::FactIndex { offset, reader } => {
1032                let repr: FactIndexRepr = reader.fetch(*offset)?;
1033                let prior = LinearFactIndex {
1034                    repr,
1035                    reader: reader.clone(),
1036                };
1037                prior.query(name, keys)
1038            }
1039        }
1040    }
1041
1042    type QueryIterator = QueryIterator;
1043    fn query_prefix(&self, name: &str, prefix: &[Bytes]) -> Result<QueryIterator, StorageError> {
1044        Ok(QueryIterator::new(
1045            self.query_prefix_inner(name, prefix)?.into_iter(),
1046        ))
1047    }
1048}
1049
1050impl<R: Read> LinearFactPerspective<R> {
1051    fn query_prefix_inner(&self, name: &str, prefix: &[Bytes]) -> Result<FactMap, StorageError> {
1052        let mut matches = match &self.prior {
1053            FactPerspectivePrior::None => BTreeMap::new(),
1054            FactPerspectivePrior::FactPerspective(prior) => {
1055                prior.query_prefix_inner(name, prefix)?
1056            }
1057            FactPerspectivePrior::FactIndex { offset, reader } => {
1058                let repr: FactIndexRepr = reader.fetch(*offset)?;
1059                let prior = LinearFactIndex {
1060                    repr,
1061                    reader: reader.clone(),
1062                };
1063                prior.query_prefix_inner(name, prefix)?
1064            }
1065        };
1066        if let Some(map) = self.map.get(name) {
1067            for (k, v) in find_prefixes(map, prefix) {
1068                // overwrite "earlier" facts
1069                matches.insert(k.clone(), v.map(Into::into));
1070            }
1071        }
1072        Ok(matches)
1073    }
1074}
1075
1076impl<R: Read> QueryMut for LinearFactPerspective<R> {
1077    fn insert(&mut self, name: String, keys: Keys, value: Bytes) -> Result<(), StorageError> {
1078        self.map.entry(name).or_default().insert(keys, Some(value));
1079        Ok(())
1080    }
1081
1082    fn delete(&mut self, name: String, keys: Keys) -> Result<(), StorageError> {
1083        if self.prior.is_none() {
1084            // No need for tombstones with no prior.
1085            if let Some(kv) = self.map.get_mut(&name) {
1086                kv.remove(&keys);
1087            }
1088        } else {
1089            self.map.entry(name).or_default().insert(keys, None);
1090        }
1091        Ok(())
1092    }
1093}
1094
1095impl<R: Read> FactPerspective for LinearPerspective<R> {}
1096
1097impl<R: Read> Query for LinearPerspective<R> {
1098    fn query(&self, name: &str, keys: &[Bytes]) -> Result<Option<Bytes>, StorageError> {
1099        self.facts.query(name, keys)
1100    }
1101
1102    type QueryIterator = QueryIterator;
1103    fn query_prefix(&self, name: &str, prefix: &[Bytes]) -> Result<QueryIterator, StorageError> {
1104        self.facts.query_prefix(name, prefix)
1105    }
1106}
1107
1108impl<R: Read> QueryMut for LinearPerspective<R> {
1109    fn insert(&mut self, name: String, keys: Keys, value: Bytes) -> Result<(), StorageError> {
1110        self.facts
1111            .insert(name.clone(), keys.clone(), value.clone())?;
1112        self.current_updates.push((name, keys, Some(value)));
1113        Ok(())
1114    }
1115
1116    fn delete(&mut self, name: String, keys: Keys) -> Result<(), StorageError> {
1117        self.facts.delete(name.clone(), keys.clone())?;
1118        self.current_updates.push((name, keys, None));
1119        Ok(())
1120    }
1121}
1122
1123impl<R: Read> Revertable for LinearPerspective<R> {
1124    fn checkpoint(&self) -> Checkpoint {
1125        Checkpoint {
1126            index: self.commands.len(),
1127        }
1128    }
1129
1130    fn revert(&mut self, checkpoint: Checkpoint) -> Result<(), StorageError> {
1131        // Equal command count alone does not mean clean: a rule that wrote
1132        // facts and then failed leaves its writes pending in
1133        // `facts`/`current_updates` without having added a command. But
1134        // every fact write pushes onto `current_updates`, so an empty
1135        // buffer at equal command count means the fact overlay is untouched
1136        // since the checkpoint and there is nothing to rebuild.
1137        if checkpoint.index == self.commands.len() && self.current_updates.is_empty() {
1138            return Ok(());
1139        }
1140
1141        if checkpoint.index > self.commands.len() {
1142            bug!(
1143                "A checkpoint's index should always be less than or equal to the length of a perspective's command history!"
1144            );
1145        }
1146
1147        self.commands.truncate(checkpoint.index);
1148        self.facts.clear();
1149        self.current_updates.clear();
1150        for data in &self.commands {
1151            self.facts.apply_updates(&data.updates)?;
1152        }
1153
1154        Ok(())
1155    }
1156}
1157
1158impl<R: Read> Perspective for LinearPerspective<R> {
1159    fn policy(&self) -> PolicyId {
1160        self.policy
1161    }
1162
1163    fn add_command(&mut self, command: &impl Command) -> Result<usize, StorageError> {
1164        if command.parent() != self.head_address()? {
1165            return Err(StorageError::PerspectiveHeadMismatch);
1166        }
1167
1168        self.commands.push(CommandData {
1169            id: command.id(),
1170            priority: command.priority(),
1171            policy: command.policy().map(Bytes::from),
1172            data: command.bytes().into(),
1173            updates: core::mem::take(&mut self.current_updates),
1174        });
1175        Ok(self.commands.len())
1176    }
1177
1178    fn includes(&self, id: CmdId) -> bool {
1179        self.commands.iter().any(|cmd| cmd.id == id)
1180    }
1181
1182    fn head_address(&self) -> Result<Prior<Address>, Bug> {
1183        Ok(if let Some(last) = self.commands.last() {
1184            Prior::Single(Address {
1185                id: last.id,
1186                max_cut: self
1187                    .max_cut
1188                    .checked_add(
1189                        self.commands
1190                            .len()
1191                            .checked_sub(1)
1192                            .assume("must not overflow")? as u64,
1193                    )
1194                    .assume("must not overflow")?,
1195            })
1196        } else {
1197            self.parents
1198        })
1199    }
1200}
1201
1202impl From<Prior<Address>> for Prior<CmdId> {
1203    fn from(p: Prior<Address>) -> Self {
1204        match p {
1205            Prior::None => Self::None,
1206            Prior::Single(l) => Self::Single(l.id),
1207            Prior::Merge(l, r) => Self::Merge(l.id, r.id),
1208        }
1209    }
1210}
1211
1212impl Command for LinearCommand<'_> {
1213    fn priority(&self) -> Priority {
1214        self.priority.clone()
1215    }
1216
1217    fn id(&self) -> CmdId {
1218        *self.id
1219    }
1220
1221    fn parent(&self) -> Prior<Address> {
1222        self.parent
1223    }
1224
1225    fn policy(&self) -> Option<&[u8]> {
1226        self.policy
1227    }
1228
1229    fn bytes(&self) -> &[u8] {
1230        self.data
1231    }
1232}
1233
1234fn find_prefixes<'m, 'p: 'm>(
1235    map: &'m FactMap,
1236    prefix: &'p [Bytes],
1237) -> impl Iterator<Item = (&'m Keys, Option<&'m [u8]>)> + 'm {
1238    map.range::<[Bytes], _>((Bound::Included(prefix), Bound::Unbounded))
1239        .take_while(|(k, _)| k.starts_with(prefix))
1240        .map(|(k, v)| (k, v.as_deref()))
1241}
1242
1243#[cfg(test)]
1244mod test {
1245    use testing::Manager;
1246
1247    use super::*;
1248    use crate::testing::dsl::{StorageBackend, test_suite};
1249
1250    #[test]
1251    fn test_query_prefix() {
1252        let mut provider = LinearStorageProvider::new(Manager::new());
1253        let mut fp = provider.new_perspective(PolicyId::new(0));
1254
1255        let name = "x";
1256
1257        let keys: &[&[&str]] = &[
1258            &["aa", "xy", "123"],
1259            &["aa", "xz", "123"],
1260            &["bb", "ccc"],
1261            &["bc", ""],
1262        ];
1263        let keys: Vec<Keys> = keys
1264            .iter()
1265            .map(|ks| ks.iter().map(|k| Bytes::from(k.as_bytes())).collect())
1266            .collect();
1267
1268        for ks in &keys {
1269            fp.insert(
1270                name.into(),
1271                ks.clone(),
1272                format!("{ks:?}").into_bytes().into(),
1273            )
1274            .unwrap();
1275        }
1276
1277        let prefixes: &[&[&str]] = &[
1278            &["aa", "xy", "12"],
1279            &["aa", "xy"],
1280            &["aa", "xz"],
1281            &["aa", "x"],
1282            &["bb", ""],
1283            &["bb", "ccc"],
1284            &["bc", ""],
1285        ];
1286
1287        for prefix in prefixes {
1288            let prefix: Keys = prefix.iter().map(|k| Bytes::from(k.as_bytes())).collect();
1289            let found: Vec<_> = fp.query_prefix(name, &prefix).unwrap().collect();
1290            let mut expected: Vec<_> = keys.iter().filter(|k| k.starts_with(&prefix)).collect();
1291            expected.sort();
1292            assert_eq!(found.len(), expected.len());
1293            for (a, b) in std::iter::zip(found, expected) {
1294                let a = a.unwrap();
1295                assert_eq!(&a.key, b);
1296                assert_eq!(a.value.as_ref(), format!("{b:?}").as_bytes());
1297            }
1298        }
1299    }
1300
1301    /// `revert` must restore the exact state captured by `checkpoint`.
1302    ///
1303    /// This mirrors how `Transaction::add_single` uses the API: the
1304    /// checkpoint is taken *before* the policy rule runs, the rule may write
1305    /// facts, and on rule failure `revert` is called before any
1306    /// `add_command`. So at revert time `checkpoint.index == commands.len()`
1307    /// always holds, and the pending fact writes must still be cleared.
1308    #[test]
1309    fn test_revert_clears_writes_made_after_checkpoint() {
1310        let mut provider = LinearStorageProvider::new(Manager::new());
1311        let mut p = provider.new_perspective(PolicyId::new(0));
1312
1313        let checkpoint = p.checkpoint();
1314        p.insert("x".into(), Keys::default(), Bytes::from(&b"1"[..]))
1315            .unwrap();
1316        p.revert(checkpoint).unwrap();
1317
1318        assert!(
1319            p.query("x", &[]).unwrap().is_none(),
1320            "revert must clear fact writes made after the checkpoint"
1321        );
1322        assert!(
1323            p.current_updates.is_empty(),
1324            "revert must clear pending updates made after the checkpoint"
1325        );
1326    }
1327
1328    struct LinearBackend;
1329    impl StorageBackend for LinearBackend {
1330        type StorageProvider = LinearStorageProvider<Manager>;
1331
1332        fn provider(&mut self, _client_id: u64) -> Self::StorageProvider {
1333            LinearStorageProvider::new(Manager::new())
1334        }
1335    }
1336    test_suite!(|| LinearBackend);
1337}