Skip to main content

yugendb_core/
store.rs

1//! User-facing store and builder types.
2
3use std::sync::Arc;
4
5use chrono::Utc;
6use serde::{de::DeserializeOwned, Serialize};
7
8use crate::{
9    Capabilities, Codec, CodecExt, Collection, CollectionName, DeleteOptions, Driver, JsonCodec,
10    Key, Namespace, ReadOptions, Result, ScanOptions, StoredValue, ValueMetadata, WriteOptions,
11    YugenDbError,
12};
13
14/// Main user-facing database handle.
15#[derive(Clone)]
16pub struct Store {
17    driver: Arc<dyn Driver>,
18    codec: Arc<dyn Codec>,
19    namespace: Namespace,
20    default_collection: CollectionName,
21}
22
23impl Store {
24    /// Creates a store builder.
25    #[must_use]
26    pub fn builder() -> StoreBuilder {
27        StoreBuilder::default()
28    }
29
30    /// Creates a store from parts.
31    #[must_use]
32    pub fn from_parts(
33        driver: Arc<dyn Driver>,
34        codec: Arc<dyn Codec>,
35        namespace: Namespace,
36    ) -> Self {
37        Self {
38            driver,
39            codec,
40            namespace,
41            default_collection: CollectionName::default(),
42        }
43    }
44
45    /// Prepares the underlying driver for use.
46    pub async fn initialise(&self) -> Result<()> {
47        self.driver.initialise().await
48    }
49
50    /// Releases underlying driver resources.
51    pub async fn finalise(&self) -> Result<()> {
52        self.driver.finalise().await
53    }
54
55    /// Returns the active namespace.
56    #[must_use]
57    pub fn namespace(&self) -> &Namespace {
58        &self.namespace
59    }
60
61    /// Returns the active driver capabilities.
62    #[must_use]
63    pub fn capabilities(&self) -> Capabilities {
64        self.driver.capabilities()
65    }
66
67    /// Reads a typed value from the default collection.
68    pub async fn get<T, K>(&self, key: K) -> Result<Option<T>>
69    where
70        T: DeserializeOwned,
71        K: TryInto<Key>,
72        YugenDbError: From<K::Error>,
73    {
74        self.get_from_collection(&self.default_collection, key)
75            .await
76    }
77
78    /// Writes a typed value to the default collection.
79    pub async fn set<T, K>(&self, key: K, value: &T) -> Result<()>
80    where
81        T: Serialize,
82        K: TryInto<Key>,
83        YugenDbError: From<K::Error>,
84    {
85        self.set_in_collection(&self.default_collection, key, value)
86            .await
87    }
88
89    /// Deletes a value from the default collection.
90    pub async fn delete<K>(&self, key: K) -> Result<bool>
91    where
92        K: TryInto<Key>,
93        YugenDbError: From<K::Error>,
94    {
95        self.delete_from_collection(&self.default_collection, key)
96            .await
97    }
98
99    /// Returns whether a key exists in the default collection.
100    pub async fn exists<K>(&self, key: K) -> Result<bool>
101    where
102        K: TryInto<Key>,
103        YugenDbError: From<K::Error>,
104    {
105        self.exists_in_collection(&self.default_collection, key)
106            .await
107    }
108
109    /// Scans typed values by key prefix in the default collection.
110    pub async fn scan_prefix<T>(&self, prefix: impl Into<String>) -> Result<Vec<(Key, T)>>
111    where
112        T: DeserializeOwned,
113    {
114        self.scan_prefix_in_collection(&self.default_collection, prefix)
115            .await
116    }
117
118    /// Creates a typed collection handle.
119    pub fn collection<T, C>(&self, name: C) -> Result<Collection<T>>
120    where
121        C: TryInto<CollectionName>,
122        YugenDbError: From<C::Error>,
123    {
124        let name = name.try_into()?;
125        Ok(Collection::new(self.clone(), name))
126    }
127
128    /// Reads a typed value from a specific collection.
129    pub async fn get_from_collection<T, K>(
130        &self,
131        collection: &CollectionName,
132        key: K,
133    ) -> Result<Option<T>>
134    where
135        T: DeserializeOwned,
136        K: TryInto<Key>,
137        YugenDbError: From<K::Error>,
138    {
139        let key = key.try_into()?;
140        let options = ReadOptions::default();
141        let Some(stored) = self
142            .driver
143            .get(&self.namespace, collection, &key, &options)
144            .await?
145        else {
146            return Ok(None);
147        };
148
149        if stored.is_expired() && !options.allow_expired {
150            return Ok(None);
151        }
152
153        self.codec.deserialise(&stored.bytes).map(Some)
154    }
155
156    /// Writes a typed value to a specific collection.
157    pub async fn set_in_collection<T, K>(
158        &self,
159        collection: &CollectionName,
160        key: K,
161        value: &T,
162    ) -> Result<()>
163    where
164        T: Serialize,
165        K: TryInto<Key>,
166        YugenDbError: From<K::Error>,
167    {
168        self.set_in_collection_with_options(collection, key, value, WriteOptions::default())
169            .await
170    }
171
172    /// Writes a typed value to a specific collection with options.
173    pub async fn set_in_collection_with_options<T, K>(
174        &self,
175        collection: &CollectionName,
176        key: K,
177        value: &T,
178        options: WriteOptions,
179    ) -> Result<()>
180    where
181        T: Serialize,
182        K: TryInto<Key>,
183        YugenDbError: From<K::Error>,
184    {
185        let key = key.try_into()?;
186        let bytes = self.codec.serialise(value)?;
187        let now = Utc::now();
188        let expires_at = match options.ttl {
189            Some(ttl) => {
190                let ttl = chrono::Duration::from_std(ttl)
191                    .map_err(|_| YugenDbError::invalid_value("ttl duration is too large"))?;
192                Some(now + ttl)
193            }
194            None => None,
195        };
196        let metadata = ValueMetadata::new(self.codec.name(), now, expires_at);
197        let stored = StoredValue::new(bytes, metadata);
198        self.driver
199            .set(&self.namespace, collection, &key, stored, &options)
200            .await
201    }
202
203    /// Deletes a value from a specific collection.
204    pub async fn delete_from_collection<K>(
205        &self,
206        collection: &CollectionName,
207        key: K,
208    ) -> Result<bool>
209    where
210        K: TryInto<Key>,
211        YugenDbError: From<K::Error>,
212    {
213        let key = key.try_into()?;
214        self.driver
215            .delete(&self.namespace, collection, &key, &DeleteOptions::default())
216            .await
217    }
218
219    /// Returns whether a key exists in a specific collection.
220    pub async fn exists_in_collection<K>(&self, collection: &CollectionName, key: K) -> Result<bool>
221    where
222        K: TryInto<Key>,
223        YugenDbError: From<K::Error>,
224    {
225        let key = key.try_into()?;
226        self.driver.exists(&self.namespace, collection, &key).await
227    }
228
229    /// Scans typed values by key prefix in a specific collection.
230    pub async fn scan_prefix_in_collection<T>(
231        &self,
232        collection: &CollectionName,
233        prefix: impl Into<String>,
234    ) -> Result<Vec<(Key, T)>>
235    where
236        T: DeserializeOwned,
237    {
238        let options = ScanOptions {
239            prefix: Some(prefix.into()),
240            ..ScanOptions::default()
241        };
242        let stored_values = self
243            .driver
244            .scan_prefix(&self.namespace, collection, &options)
245            .await?;
246
247        stored_values
248            .into_iter()
249            .filter(|(_, stored)| options.include_expired || !stored.is_expired())
250            .map(|(key, stored)| {
251                self.codec
252                    .deserialise(&stored.bytes)
253                    .map(|value| (key, value))
254            })
255            .collect()
256    }
257}
258
259/// Builder for [`Store`].
260#[derive(Clone)]
261pub struct StoreBuilder {
262    driver: Option<Arc<dyn Driver>>,
263    codec: Arc<dyn Codec>,
264    namespace: Namespace,
265}
266
267impl Default for StoreBuilder {
268    fn default() -> Self {
269        Self {
270            driver: None,
271            codec: Arc::new(JsonCodec),
272            namespace: Namespace::default(),
273        }
274    }
275}
276
277impl StoreBuilder {
278    /// Sets the driver.
279    #[must_use]
280    pub fn driver<D>(mut self, driver: D) -> Self
281    where
282        D: Driver + 'static,
283    {
284        self.driver = Some(Arc::new(driver));
285        self
286    }
287
288    /// Sets an already shared driver.
289    #[must_use]
290    pub fn driver_arc(mut self, driver: Arc<dyn Driver>) -> Self {
291        self.driver = Some(driver);
292        self
293    }
294
295    /// Sets the codec.
296    #[must_use]
297    pub fn codec<C>(mut self, codec: C) -> Self
298    where
299        C: Codec + 'static,
300    {
301        self.codec = Arc::new(codec);
302        self
303    }
304
305    /// Sets the namespace.
306    pub fn namespace<N>(mut self, namespace: N) -> Result<Self>
307    where
308        N: TryInto<Namespace>,
309        YugenDbError: From<N::Error>,
310    {
311        self.namespace = namespace.try_into()?;
312        Ok(self)
313    }
314
315    /// Builds and initialises the store.
316    pub async fn connect(self) -> Result<Store> {
317        let store = self.build()?;
318        store.initialise().await?;
319        Ok(store)
320    }
321
322    /// Builds the store without initialising the driver.
323    pub fn build(self) -> Result<Store> {
324        let Some(driver) = self.driver else {
325            return Err(YugenDbError::invalid_value("store driver is required"));
326        };
327
328        Ok(Store::from_parts(driver, self.codec, self.namespace))
329    }
330}