iroh_docs/store/fs/
ranges.rs

1//! Ranges and helpers for working with [`redb`] tables
2
3use redb::{Key, Range, ReadOnlyTable, ReadableTable, Value};
4
5use super::{
6    bounds::{ByKeyBounds, RecordsBounds},
7    into_entry,
8    tables::{RecordsByKeyId, RecordsId, RecordsValue},
9};
10use crate::{store::SortDirection, SignedEntry};
11
12/// An extension trait for [`Range`] that provides methods for mapped retrieval.
13pub trait RangeExt<K: Key, V: Value> {
14    /// Get the next entry and map the item with a callback function.
15    fn next_map<T>(
16        &mut self,
17        map: impl for<'x> Fn(K::SelfType<'x>, V::SelfType<'x>) -> T,
18    ) -> Option<anyhow::Result<T>>;
19
20    /// Get the next entry, but only if the callback function returns Some, otherwise continue.
21    ///
22    /// With `direction` the range can be either process in forward or backward direction.
23    fn next_filter_map<T>(
24        &mut self,
25        direction: &SortDirection,
26        filter_map: impl for<'x> Fn(K::SelfType<'x>, V::SelfType<'x>) -> Option<T>,
27    ) -> Option<anyhow::Result<T>>;
28
29    /// Like [`Self::next_filter_map`], but the callback returns a `Result`, and the result is
30    /// flattened with the result from the range operation.
31    fn next_try_filter_map<T>(
32        &mut self,
33        direction: &SortDirection,
34        filter_map: impl for<'x> Fn(K::SelfType<'x>, V::SelfType<'x>) -> Option<anyhow::Result<T>>,
35    ) -> Option<anyhow::Result<T>> {
36        Some(self.next_filter_map(direction, filter_map)?.and_then(|r| r))
37    }
38}
39
40impl<K: Key + 'static, V: Value + 'static> RangeExt<K, V> for Range<'_, K, V> {
41    fn next_map<T>(
42        &mut self,
43        map: impl for<'x> Fn(K::SelfType<'x>, V::SelfType<'x>) -> T,
44    ) -> Option<anyhow::Result<T>> {
45        self.next()
46            .map(|r| r.map_err(Into::into).map(|r| map(r.0.value(), r.1.value())))
47    }
48
49    fn next_filter_map<T>(
50        &mut self,
51        direction: &SortDirection,
52        filter_map: impl for<'x> Fn(K::SelfType<'x>, V::SelfType<'x>) -> Option<T>,
53    ) -> Option<anyhow::Result<T>> {
54        loop {
55            let next = match direction {
56                SortDirection::Asc => self.next(),
57                SortDirection::Desc => self.next_back(),
58            };
59            match next {
60                None => break None,
61                Some(Err(err)) => break Some(Err(err.into())),
62                Some(Ok(res)) => match filter_map(res.0.value(), res.1.value()) {
63                    None => continue,
64                    Some(item) => break Some(Ok(item)),
65                },
66            }
67        }
68    }
69}
70
71/// An iterator over a range of entries from the records table.
72#[derive(derive_more::Debug)]
73#[debug("RecordsRange")]
74pub struct RecordsRange<'a>(Range<'a, RecordsId<'static>, RecordsValue<'static>>);
75
76// pub type RecordsRange<'a> = Range<'a, RecordsId<'static>, RecordsValue<'static>>;
77
78impl<'a> RecordsRange<'a> {
79    pub(super) fn with_bounds(
80        records: &'a impl ReadableTable<RecordsId<'static>, RecordsValue<'static>>,
81        bounds: RecordsBounds,
82    ) -> anyhow::Result<Self> {
83        let range = records.range(bounds.as_ref())?;
84        Ok(Self(range))
85    }
86
87    //
88    /// Get the next item in the range.
89    ///
90    /// Omit items for which the `matcher` function returns false.
91    pub(super) fn next_filtered(
92        &mut self,
93        direction: &SortDirection,
94        filter: impl for<'x> Fn(RecordsId<'x>, RecordsValue<'x>) -> bool,
95    ) -> Option<anyhow::Result<SignedEntry>> {
96        self.0
97            .next_filter_map(direction, |k, v| filter(k, v).then(|| into_entry(k, v)))
98    }
99}
100
101impl RecordsRange<'static> {
102    pub(super) fn all_static(
103        records: &ReadOnlyTable<RecordsId<'static>, RecordsValue<'static>>,
104    ) -> anyhow::Result<Self> {
105        let range = records.range::<RecordsId<'static>>(..)?;
106        Ok(Self(range))
107    }
108    pub(super) fn with_bounds_static(
109        records: &ReadOnlyTable<RecordsId<'static>, RecordsValue<'static>>,
110        bounds: RecordsBounds,
111    ) -> anyhow::Result<Self> {
112        let range = records.range(bounds.as_ref())?;
113        Ok(Self(range))
114    }
115}
116
117impl Iterator for RecordsRange<'_> {
118    type Item = anyhow::Result<SignedEntry>;
119    fn next(&mut self) -> Option<Self::Item> {
120        self.0.next_map(into_entry)
121    }
122}
123
124#[derive(derive_more::Debug)]
125#[debug("RecordsByKeyRange")]
126pub struct RecordsByKeyRange {
127    records_table: ReadOnlyTable<RecordsId<'static>, RecordsValue<'static>>,
128    by_key_range: Range<'static, RecordsByKeyId<'static>, ()>,
129}
130
131impl RecordsByKeyRange {
132    pub fn with_bounds(
133        records_by_key_table: ReadOnlyTable<RecordsByKeyId<'static>, ()>,
134        records_table: ReadOnlyTable<RecordsId<'static>, RecordsValue<'static>>,
135        bounds: ByKeyBounds,
136    ) -> anyhow::Result<Self> {
137        let by_key_range = records_by_key_table.range(bounds.as_ref())?;
138        Ok(Self {
139            records_table,
140            by_key_range,
141        })
142    }
143
144    /// Get the next item in the range.
145    ///
146    /// Omit items for which the `filter` function returns false.
147    pub fn next_filtered(
148        &mut self,
149        direction: &SortDirection,
150        filter: impl for<'x> Fn(RecordsByKeyId<'x>) -> bool,
151    ) -> Option<anyhow::Result<SignedEntry>> {
152        let entry = self.by_key_range.next_try_filter_map(direction, |k, _v| {
153            if !filter(k) {
154                return None;
155            };
156            let (namespace, key, author) = k;
157            let records_id = (namespace, author, key);
158            let entry = self.records_table.get(&records_id).transpose()?;
159            let entry = entry
160                .map(|value| into_entry(records_id, value.value()))
161                .map_err(anyhow::Error::from);
162            Some(entry)
163        });
164        entry
165    }
166}