url_cleaner_engine/glue/caching/
inner.rs

1//! The home if [`InnerCache`].
2
3use std::cell::OnceCell;
4
5use diesel::prelude::*;
6#[expect(unused_imports, reason = "Used in docs.")]
7use diesel::query_builder::SqlQuery;
8
9use crate::glue::*;
10use crate::util::*;
11
12/// A lazily connected connection to a Sqlite database.
13/// # Examples
14/// ```
15/// use url_cleaner_engine::glue::*;
16/// use std::time::Duration;
17///
18/// // Note the mutability.
19/// let mut cache = InnerCache::new(CachePath::Memory);
20///
21/// assert_eq!(cache.read(CacheEntryKeys { subject: "subject", key: "key" }).unwrap().map(|entry| entry.value), None);
22/// cache.write(NewCacheEntry { subject: "subject", key: "key", value: None, duration: Default::default() }).unwrap();
23/// assert_eq!(cache.read(CacheEntryKeys { subject: "subject", key: "key" }).unwrap().map(|entry| entry.value), Some(None));
24/// cache.write(NewCacheEntry { subject: "subject", key: "key", value: Some("value"), duration: Default::default() }).unwrap();
25/// assert_eq!(cache.read(CacheEntryKeys { subject: "subject", key: "key" }).unwrap().map(|entry| entry.value), Some(Some("value".into())));
26/// ```
27#[derive(Default)]
28pub struct InnerCache {
29    /// The path of the database.
30    path: CachePath,
31    /// The connection to the database.
32    connection: OnceCell<SqliteConnection>
33}
34
35impl ::core::fmt::Debug for InnerCache {
36    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
37        f.debug_struct("InnerCache")
38            .field("path", &self.path)
39            .field("connection", if self.connection.get().is_some() {&"OnceCell(..)"} else {&"OnceCell(<uninit>)"})
40            .finish()
41    }
42}
43
44impl InnerCache {
45    /// Create a new unconnected [`Self`].
46    pub fn new(path: CachePath) -> Self {
47        path.into()
48    }
49}
50
51impl PartialEq for InnerCache {
52    fn eq(&self, other: &Self) -> bool {
53        self.path == other.path
54    }
55}
56impl Eq for InnerCache {}
57
58impl From<CachePath> for InnerCache {
59    fn from(value: CachePath) -> Self {
60        Self {
61            path: value,
62            ..Default::default()
63        }
64    }
65}
66
67impl InnerCache {
68    /// Gets the [`CachePath`] of the connection.
69    pub fn path(&self) -> &CachePath {
70        &self.path
71    }
72
73    /// Gets the connection itself, if `self` has been connected via [`Self::connect`] yet.
74    pub fn connection(&mut self) -> Option<&mut SqliteConnection> {
75        self.connection.get_mut()
76    }
77
78    /// Returns the connection, connecting if not already connected.
79    /// # Errors
80    /// If the call to [`std::fs::exists`] to check if the database exists returns an error, that error is returned.
81    ///
82    /// If the call to [`std::fs::File::create_new`] to create the database returns an error, that error is returned.
83    ///
84    /// If the call to [`SqliteConnection::establish`] to connect to the database returns an error, that error is returned.
85    ///
86    /// If the call to [`SqlQuery::execute`] to initialize the database returns an error, that error is returned.
87    #[allow(clippy::missing_panics_doc, reason = "Doesn't panic, but should be replaced with OnceCell::get_or_try_init once that's stable.")]
88    pub fn connect(&mut self) -> Result<&mut SqliteConnection, ConnectCacheError> {
89        debug!(InnerCache::connect, self);
90        if self.connection.get().is_none() {
91            let mut needs_init = self.path == CachePath::Memory;
92            if let CachePath::Path(path) = &self.path && !std::fs::exists(path)? {
93                needs_init = true;
94                std::fs::File::create_new(path)?;
95            }
96            let mut connection = SqliteConnection::establish(self.path.as_str())?;
97            if needs_init {
98                diesel::sql_query(DB_INIT_COMMAND).execute(&mut connection)?;
99            }
100            self.connection.set(connection).map_err(|_| ()).expect("The connection to have just been confirmed unset.");
101        }
102        Ok(self.connection.get_mut().expect("The connection to have just been set."))
103    }
104
105    /// Disconnects from the database.
106    pub fn disconnect(&mut self) {
107        let _ = self.connection.take();
108    }
109
110    /// Reads from the database.
111    /// # Errors
112    /// If the call to [`Self::connect`] returns an error, that error is returned.
113    ///
114    /// If the call to [`RunQueryDsl::load`] returns an error, that error is returned.
115    pub fn read(&mut self, keys: CacheEntryKeys) -> Result<Option<CacheEntryValues>, ReadFromCacheError> {
116        debug!(InnerCache::read, self, keys);
117        Ok(cache::dsl::cache
118            .filter(cache::dsl::subject.eq(keys.subject))
119            .filter(cache::dsl::key.eq(keys.key))
120            .limit(1)
121            .select(CacheEntryValues::as_select())
122            .load(self.connect()?)?
123            .first().cloned())
124    }
125
126    /// Writes to the database, overwriting the entry the equivalent call to [`Self::read`] would return.
127    /// # Errors
128    /// If the call to [`Self::connect`] returns an error, that error is returned.
129    ///
130    /// If the call to [`RunQueryDsl::get_result`] returns an error, that error is returned.
131    pub fn write(&mut self, entry: NewCacheEntry) -> Result<(), WriteToCacheError> {
132        debug!(InnerCache::write, self, entry);
133        diesel::insert_into(cache::table)
134            .values(entry)
135            .execute(self.connect()?)?;
136        Ok(())
137    }
138}
139
140impl From<InnerCache> for (CachePath, OnceCell<SqliteConnection>) {
141    fn from(value: InnerCache) -> Self {
142        (value.path, value.connection)
143    }
144}