sui_castore/storage/
index.rs1use std::path::Path;
9
10use redb::{Database, ReadableTable, ReadableTableMetadata, TableDefinition};
11use tracing::{debug, info};
12
13use crate::StoreError;
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, StoreError> {
35 let db = Database::create(path)
36 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb open: {e}"))))?;
37
38 let write_txn = db
40 .begin_write()
41 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
42 {
43 let _ = write_txn.open_table(NARINFO_TABLE);
44 let _ = write_txn.open_table(STORE_PATH_TABLE);
45 }
46 write_txn
47 .commit()
48 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
49
50 info!(path = %path.display(), "Opened redb index");
51 Ok(Self { db })
52 }
53
54 pub fn index_narinfo(&self, hash: &str, nar_size: u64) -> Result<(), StoreError> {
60 let now = std::time::SystemTime::now()
61 .duration_since(std::time::UNIX_EPOCH)
62 .unwrap_or_default()
63 .as_secs();
64
65 let write_txn = self.db.begin_write()
66 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
67 {
68 let mut table = write_txn.open_table(NARINFO_TABLE)
69 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
70 table.insert(hash, (now, nar_size))
71 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb insert: {e}"))))?;
72 }
73 write_txn.commit()
74 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
75
76 debug!(hash = %hash, nar_size, "Indexed narinfo");
77 Ok(())
78 }
79
80 pub fn index_store_path(&self, store_path: &str, hash: &str) -> Result<(), StoreError> {
86 let write_txn = self.db.begin_write()
87 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
88 {
89 let mut table = write_txn.open_table(STORE_PATH_TABLE)
90 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
91 table.insert(store_path, hash)
92 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb insert: {e}"))))?;
93 }
94 write_txn.commit()
95 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
96 Ok(())
97 }
98
99 pub fn has_narinfo(&self, hash: &str) -> Result<bool, StoreError> {
105 let read_txn = self.db.begin_read()
106 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
107 let table = read_txn.open_table(NARINFO_TABLE)
108 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
109 let exists = table.get(hash)
110 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb get: {e}"))))?
111 .is_some();
112 Ok(exists)
113 }
114
115 pub fn list_hashes(&self) -> Result<Vec<String>, StoreError> {
121 let read_txn = self.db.begin_read()
122 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
123 let table = read_txn.open_table(NARINFO_TABLE)
124 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
125
126 let mut hashes = Vec::new();
127 let iter = table.iter()
128 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb iter: {e}"))))?;
129 for entry in iter {
130 let (key, _) = entry
131 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb entry: {e}"))))?;
132 hashes.push(key.value().to_string());
133 }
134 Ok(hashes)
135 }
136
137 pub fn count(&self) -> Result<u64, StoreError> {
143 let read_txn = self.db.begin_read()
144 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
145 let table = read_txn.open_table(NARINFO_TABLE)
146 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
147 table.len()
148 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb len: {e}"))))
149 }
150
151 pub fn remove(&self, hash: &str) -> Result<(), StoreError> {
157 let write_txn = self.db.begin_write()
158 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb txn: {e}"))))?;
159 {
160 let mut table = write_txn.open_table(NARINFO_TABLE)
161 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb table: {e}"))))?;
162 let _ = table.remove(hash);
163 }
164 write_txn.commit()
165 .map_err(|e| StoreError::Io(std::io::Error::other(format!("redb commit: {e}"))))?;
166 Ok(())
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn index_roundtrip() {
176 let dir = tempfile::tempdir().unwrap();
177 let db_path = dir.path().join("test.redb");
178 let idx = StorageIndex::open(&db_path).unwrap();
179
180 idx.index_narinfo("abc123", 1024).unwrap();
182 assert!(idx.has_narinfo("abc123").unwrap());
183 assert!(!idx.has_narinfo("xyz789").unwrap());
184 assert_eq!(idx.count().unwrap(), 1);
185
186 let hashes = idx.list_hashes().unwrap();
188 assert_eq!(hashes, vec!["abc123"]);
189
190 idx.remove("abc123").unwrap();
192 assert!(!idx.has_narinfo("abc123").unwrap());
193 assert_eq!(idx.count().unwrap(), 0);
194 }
195
196 #[test]
197 fn store_path_mapping() {
198 let dir = tempfile::tempdir().unwrap();
199 let db_path = dir.path().join("test.redb");
200 let idx = StorageIndex::open(&db_path).unwrap();
201
202 idx.index_store_path("abc123-hello", "abc123").unwrap();
203 }
204}