Skip to main content

spire_core/dataset/
data.rs

1use std::fmt;
2use std::future::Future;
3use std::sync::Arc;
4
5use futures::future::BoxFuture;
6use futures::{FutureExt, Stream, stream};
7
8use crate::Result;
9use crate::dataset::{Dataset, READ_BATCH};
10
11/// A cloneable, type-erased handle to a [`Dataset`].
12pub struct Data<T> {
13    inner: Arc<dyn DynDataset<T>>,
14}
15
16impl<T: Send + 'static> Data<T> {
17    /// Wraps `dataset`.
18    #[must_use]
19    pub fn new(dataset: impl Dataset<T>) -> Self {
20        Self {
21            inner: Arc::new(dataset),
22        }
23    }
24
25    /// Stores an item.
26    ///
27    /// # Errors
28    ///
29    /// Returns an error if the underlying dataset fails.
30    pub async fn write(&self, item: T) -> Result<()> {
31        self.inner.write(item).await
32    }
33
34    /// Stores every item of `items` at once, see [`Dataset::write_batch`].
35    ///
36    /// # Errors
37    ///
38    /// Returns an error if the underlying dataset fails.
39    pub async fn write_all(&self, items: impl IntoIterator<Item = T>) -> Result<()> {
40        let items: Vec<T> = items.into_iter().collect();
41        if items.is_empty() {
42            return Ok(());
43        }
44        self.inner.write_batch(items).await
45    }
46
47    /// Takes the next item, or `None` if the dataset is empty.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if the underlying dataset fails.
52    pub async fn read(&self) -> Result<Option<T>> {
53        self.inner.read().await
54    }
55
56    /// Takes up to `max` items at once, see [`Dataset::read_batch`].
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if the underlying dataset fails.
61    pub async fn read_batch(&self, max: usize) -> Result<Vec<T>> {
62        self.inner.read_batch(max).await
63    }
64
65    /// Takes every item until the dataset is empty, in batches of 1024 items.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if the underlying dataset fails.
70    pub async fn read_all(&self) -> Result<Vec<T>> {
71        let mut items = Vec::with_capacity(self.size_hint().unwrap_or_default());
72        loop {
73            let batch = self.inner.read_batch(READ_BATCH).await?;
74            let last = batch.len() < READ_BATCH;
75            items.extend(batch);
76            if last {
77                return Ok(items);
78            }
79        }
80    }
81
82    /// Returns a stream that takes items one at a time until the dataset is
83    /// empty, so it never takes an item it does not yield. To drain a large
84    /// dataset, [`into_batches`](Self::into_batches) takes fewer trips to
85    /// its storage.
86    pub fn into_stream(self) -> impl Stream<Item = Result<T>> + Send {
87        stream::try_unfold(self, |data| async move {
88            let item = data.read().await?;
89            Ok(item.map(|item| (item, data)))
90        })
91    }
92
93    /// Returns a stream that takes batches of up to `max` items, see
94    /// [`Dataset::read_batch`], until the dataset is empty.
95    pub fn into_batches(self, max: usize) -> impl Stream<Item = Result<Vec<T>>> + Send {
96        let max = max.max(1);
97        stream::try_unfold((self, false), move |(data, drained)| async move {
98            if drained {
99                return Ok(None);
100            }
101            let batch = data.read_batch(max).await?;
102            if batch.is_empty() {
103                return Ok(None);
104            }
105            let drained = batch.len() < max;
106            Ok(Some((batch, (data, drained))))
107        })
108    }
109
110    /// Returns the number of stored items, if known.
111    #[must_use]
112    pub fn size_hint(&self) -> Option<usize> {
113        self.inner.size_hint()
114    }
115}
116
117impl<T> Clone for Data<T> {
118    fn clone(&self) -> Self {
119        Self {
120            inner: self.inner.clone(),
121        }
122    }
123}
124
125impl<T> fmt::Debug for Data<T> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.debug_struct("Data")
128            .field("type", &std::any::type_name::<T>())
129            .finish_non_exhaustive()
130    }
131}
132
133impl<T: Send + 'static> Dataset<T> for Data<T> {
134    fn write(&self, item: T) -> impl Future<Output = Result<()>> + Send {
135        self.inner.write(item)
136    }
137
138    fn write_batch(&self, items: Vec<T>) -> impl Future<Output = Result<()>> + Send {
139        self.inner.write_batch(items)
140    }
141
142    fn read(&self) -> impl Future<Output = Result<Option<T>>> + Send {
143        self.inner.read()
144    }
145
146    fn read_batch(&self, max: usize) -> impl Future<Output = Result<Vec<T>>> + Send {
147        self.inner.read_batch(max)
148    }
149
150    fn size_hint(&self) -> Option<usize> {
151        self.inner.size_hint()
152    }
153}
154
155/// An object-safe [`Dataset`].
156trait DynDataset<T>: Send + Sync + 'static {
157    fn write(&self, item: T) -> BoxFuture<'_, Result<()>>;
158    fn write_batch(&self, items: Vec<T>) -> BoxFuture<'_, Result<()>>;
159    fn read(&self) -> BoxFuture<'_, Result<Option<T>>>;
160    fn read_batch(&self, max: usize) -> BoxFuture<'_, Result<Vec<T>>>;
161    fn size_hint(&self) -> Option<usize>;
162}
163
164impl<T, D> DynDataset<T> for D
165where
166    T: Send + 'static,
167    D: Dataset<T>,
168{
169    fn write(&self, item: T) -> BoxFuture<'_, Result<()>> {
170        Dataset::write(self, item).boxed()
171    }
172
173    fn write_batch(&self, items: Vec<T>) -> BoxFuture<'_, Result<()>> {
174        Dataset::write_batch(self, items).boxed()
175    }
176
177    fn read(&self) -> BoxFuture<'_, Result<Option<T>>> {
178        Dataset::read(self).boxed()
179    }
180
181    fn read_batch(&self, max: usize) -> BoxFuture<'_, Result<Vec<T>>> {
182        Dataset::read_batch(self, max).boxed()
183    }
184
185    fn size_hint(&self) -> Option<usize> {
186        Dataset::size_hint(self)
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use futures::TryStreamExt;
193
194    use super::*;
195    use crate::dataset::InMemDataset;
196
197    #[tokio::test]
198    async fn batch_and_stream() {
199        let data = Data::new(InMemDataset::new());
200        data.write_all([1, 2, 3]).await.unwrap();
201        assert_eq!(data.size_hint(), Some(3));
202        assert_eq!(data.read().await.unwrap(), Some(1));
203        assert_eq!(
204            data.clone()
205                .into_stream()
206                .try_collect::<Vec<_>>()
207                .await
208                .unwrap(),
209            [2, 3]
210        );
211        assert_eq!(data.read_all().await.unwrap(), Vec::<i32>::new());
212    }
213
214    #[tokio::test]
215    async fn batches_stream_until_the_dataset_is_empty() {
216        let data = Data::new((1..=5).collect::<InMemDataset<u32>>());
217        let batches: Vec<Vec<u32>> = data.clone().into_batches(2).try_collect().await.unwrap();
218        assert_eq!(batches, [vec![1, 2], vec![3, 4], vec![5]]);
219
220        // A last full batch ends on the next, empty read.
221        data.write_all([6, 7]).await.unwrap();
222        let batches: Vec<Vec<u32>> = data.clone().into_batches(2).try_collect().await.unwrap();
223        assert_eq!(batches, [vec![6, 7]]);
224        let batches: Vec<Vec<u32>> = data.into_batches(2).try_collect().await.unwrap();
225        assert_eq!(batches, Vec::<Vec<u32>>::new());
226    }
227}