Skip to main content

swiftide_integrations/redb/
node_cache.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use redb::ReadableDatabase;
4use swiftide_core::{NodeCache, indexing::TextNode};
5
6use super::Redb;
7
8// Simple proc macro that gets the ok value of a result or logs the error and returns false (not
9// cached)
10//
11// The underlying issue is that redb can be fickly if panics happened. We just want to make sure it
12// does not become worse. There probably is a better solution.
13macro_rules! unwrap_or_log {
14    ($result:expr) => {
15        match $result {
16            Ok(value) => value,
17            Err(e) => {
18                tracing::error!("Error: {:#}", e);
19                debug_assert!(
20                    true,
21                    "Redb should not give errors unless in very weird situations; this is a bug: {:#}",
22                    e
23                );
24                return false;
25            }
26        }
27    };
28}
29#[async_trait]
30impl NodeCache for Redb {
31    type Input = String;
32
33    async fn get(&self, node: &TextNode) -> bool {
34        let table_definition = self.table_definition();
35        let read_txn = unwrap_or_log!(self.database.begin_read());
36
37        let result = read_txn.open_table(table_definition);
38
39        let table = match result {
40            Ok(table) => table,
41            Err(redb::TableError::TableDoesNotExist { .. }) => {
42                // Create the table
43                {
44                    let write_txn = unwrap_or_log!(self.database.begin_write());
45
46                    unwrap_or_log!(write_txn.open_table(table_definition));
47                    unwrap_or_log!(write_txn.commit());
48                }
49
50                let read_tx = unwrap_or_log!(self.database.begin_read());
51                unwrap_or_log!(read_tx.open_table(table_definition))
52            }
53            Err(e) => {
54                tracing::error!("Failed to open table: {e:#}");
55                return false;
56            }
57        };
58
59        match table.get(self.node_key(node)).unwrap() {
60            Some(access_guard) => access_guard.value(),
61            None => false,
62        }
63    }
64
65    async fn set(&self, node: &TextNode) {
66        self.set_by_id(node.parent_id().unwrap_or_else(|| node.id()))
67            .await;
68    }
69
70    async fn set_by_id(&self, id: uuid::Uuid) {
71        let write_txn = self.database.begin_write().unwrap();
72
73        {
74            let mut table = write_txn.open_table(self.table_definition()).unwrap();
75
76            table.insert(self.node_key_for_id(id), true).unwrap();
77        }
78        write_txn.commit().unwrap();
79    }
80
81    /// Deletes the full cache table from the database.
82    async fn clear(&self) -> Result<()> {
83        let write_txn = self.database.begin_write().unwrap();
84        let _ = write_txn.delete_table(self.table_definition());
85
86        write_txn.commit().unwrap();
87
88        Ok(())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use swiftide_core::indexing::TextNode;
96    use temp_dir::TempDir;
97
98    fn setup_redb() -> Redb {
99        let tempdir = TempDir::new().unwrap();
100        Redb::builder()
101            .database_path(tempdir.child("test_clear"))
102            .build()
103            .unwrap()
104    }
105
106    #[tokio::test]
107    async fn test_get_set() {
108        let redb = setup_redb();
109        let node = TextNode::new("test_get_set");
110        assert!(!redb.get(&node).await);
111        redb.set(&node).await;
112        assert!(redb.get(&node).await);
113    }
114    #[tokio::test]
115    async fn test_clear() {
116        let redb = setup_redb();
117        let node = TextNode::new("test_clear");
118        redb.set(&node).await;
119        assert!(redb.get(&node).await);
120        redb.clear().await.unwrap();
121        assert!(!redb.get(&node).await);
122    }
123}