sui_cache/storage/
index.rs1use std::path::Path;
9
10use redb::{Database, ReadableTable, ReadableTableMetadata, TableDefinition};
11use tracing::{debug, info};
12
13use crate::CacheError;
14
15const NARINFO_TABLE: TableDefinition<&str, (u64, u64)> = TableDefinition::new("narinfos");
17const STORE_PATH_TABLE: TableDefinition<&str, &str> = TableDefinition::new("store_paths");
20pub struct StorageIndex {
24 db: Database,
25}
26
27impl StorageIndex {
28 pub fn open(path: &Path) -> Result<Self, CacheError> {
30 let db = Database::create(path)
31 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb open: {e}"))))?;
32
33 let write_txn = db
35 .begin_write()
36 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
37 {
38 let _ = write_txn.open_table(NARINFO_TABLE);
39 let _ = write_txn.open_table(STORE_PATH_TABLE);
40 }
41 write_txn
42 .commit()
43 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
44
45 info!(path = %path.display(), "Opened redb index");
46 Ok(Self { db })
47 }
48
49 pub fn index_narinfo(&self, hash: &str, nar_size: u64) -> Result<(), CacheError> {
51 let now = std::time::SystemTime::now()
52 .duration_since(std::time::UNIX_EPOCH)
53 .unwrap_or_default()
54 .as_secs();
55
56 let write_txn = self.db.begin_write()
57 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
58 {
59 let mut table = write_txn.open_table(NARINFO_TABLE)
60 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
61 table.insert(hash, (now, nar_size))
62 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb insert: {e}"))))?;
63 }
64 write_txn.commit()
65 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
66
67 debug!(hash = %hash, nar_size, "Indexed narinfo");
68 Ok(())
69 }
70
71 pub fn index_store_path(&self, store_path: &str, hash: &str) -> Result<(), CacheError> {
73 let write_txn = self.db.begin_write()
74 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
75 {
76 let mut table = write_txn.open_table(STORE_PATH_TABLE)
77 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
78 table.insert(store_path, hash)
79 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb insert: {e}"))))?;
80 }
81 write_txn.commit()
82 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
83 Ok(())
84 }
85
86 pub fn has_narinfo(&self, hash: &str) -> Result<bool, CacheError> {
88 let read_txn = self.db.begin_read()
89 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
90 let table = read_txn.open_table(NARINFO_TABLE)
91 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
92 let exists = table.get(hash)
93 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb get: {e}"))))?
94 .is_some();
95 Ok(exists)
96 }
97
98 pub fn list_hashes(&self) -> Result<Vec<String>, CacheError> {
100 let read_txn = self.db.begin_read()
101 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
102 let table = read_txn.open_table(NARINFO_TABLE)
103 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
104
105 let mut hashes = Vec::new();
106 let iter = table.iter()
107 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb iter: {e}"))))?;
108 for entry in iter {
109 let (key, _) = entry
110 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb entry: {e}"))))?;
111 hashes.push(key.value().to_string());
112 }
113 Ok(hashes)
114 }
115
116 pub fn count(&self) -> Result<u64, CacheError> {
118 let read_txn = self.db.begin_read()
119 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
120 let table = read_txn.open_table(NARINFO_TABLE)
121 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
122 table.len()
123 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb len: {e}"))))
124 }
125
126 pub fn remove(&self, hash: &str) -> Result<(), CacheError> {
128 let write_txn = self.db.begin_write()
129 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
130 {
131 let mut table = write_txn.open_table(NARINFO_TABLE)
132 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
133 let _ = table.remove(hash);
134 }
135 write_txn.commit()
136 .map_err(|e| CacheError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
137 Ok(())
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn index_roundtrip() {
147 let dir = tempfile::tempdir().unwrap();
148 let db_path = dir.path().join("test.redb");
149 let idx = StorageIndex::open(&db_path).unwrap();
150
151 idx.index_narinfo("abc123", 1024).unwrap();
153 assert!(idx.has_narinfo("abc123").unwrap());
154 assert!(!idx.has_narinfo("xyz789").unwrap());
155 assert_eq!(idx.count().unwrap(), 1);
156
157 let hashes = idx.list_hashes().unwrap();
159 assert_eq!(hashes, vec!["abc123"]);
160
161 idx.remove("abc123").unwrap();
163 assert!(!idx.has_narinfo("abc123").unwrap());
164 assert_eq!(idx.count().unwrap(), 0);
165 }
166
167 #[test]
168 fn store_path_mapping() {
169 let dir = tempfile::tempdir().unwrap();
170 let db_path = dir.path().join("test.redb");
171 let idx = StorageIndex::open(&db_path).unwrap();
172
173 idx.index_store_path("abc123-hello", "abc123").unwrap();
174 }
175}