swiftide_integrations/redb/
node_cache.rs

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