/ abzu-core / src / storage / backend.rs
backend.rs
  1  //! Storage Backend Trait
  2  //!
  3  //! Pluggable persistence layer. Currently implements Sled,
  4  //! with redb and SQLite planned for future.
  5  
  6  use super::StorageError;
  7  
  8  /// Pluggable storage backend trait
  9  ///
 10  /// All operations are raw bytes - serialization is handled by higher layers.
 11  pub trait StorageBackend: Send + Sync {
 12      /// Get a value by key
 13      fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError>;
 14      
 15      /// Put a value
 16      fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StorageError>;
 17      
 18      /// Delete a key
 19      fn delete(&self, key: &[u8]) -> Result<bool, StorageError>;
 20      
 21      /// Scan all keys with a given prefix
 22      fn scan_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError>;
 23      
 24      /// Delete all keys with a given prefix (GDPR delete)
 25      /// Returns count of deleted keys
 26      fn delete_prefix(&self, prefix: &[u8]) -> Result<u64, StorageError>;
 27      
 28      /// Flush to disk
 29      fn flush(&self) -> Result<(), StorageError>;
 30  }
 31  
 32  // ─────────────────────────────────────────────────────────────────────────────
 33  // Sled Backend
 34  // ─────────────────────────────────────────────────────────────────────────────
 35  
 36  /// Sled-based storage backend
 37  pub struct SledBackend {
 38      db: sled::Db,
 39      tree: sled::Tree,
 40  }
 41  
 42  impl SledBackend {
 43      /// Create a new Sled backend
 44      pub fn new(db: sled::Db) -> Result<Self, StorageError> {
 45          let tree = db.open_tree("user_data")?;
 46          Ok(Self { db, tree })
 47      }
 48      
 49      /// Open or create a Sled backend at the given path
 50      pub fn open(path: &std::path::Path) -> Result<Self, StorageError> {
 51          let db = sled::open(path)?;
 52          Self::new(db)
 53      }
 54  }
 55  
 56  impl StorageBackend for SledBackend {
 57      fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
 58          Ok(self.tree.get(key)?.map(|v| v.to_vec()))
 59      }
 60      
 61      fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StorageError> {
 62          self.tree.insert(key, value)?;
 63          Ok(())
 64      }
 65      
 66      fn delete(&self, key: &[u8]) -> Result<bool, StorageError> {
 67          Ok(self.tree.remove(key)?.is_some())
 68      }
 69      
 70      fn scan_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
 71          let mut results = Vec::new();
 72          for item in self.tree.scan_prefix(prefix) {
 73              let (k, v) = item?;
 74              results.push((k.to_vec(), v.to_vec()));
 75          }
 76          Ok(results)
 77      }
 78      
 79      fn delete_prefix(&self, prefix: &[u8]) -> Result<u64, StorageError> {
 80          let mut count = 0u64;
 81          let keys: Vec<_> = self.tree.scan_prefix(prefix)
 82              .filter_map(|r| r.ok().map(|(k, _)| k))
 83              .collect();
 84          
 85          for key in keys {
 86              if self.tree.remove(&key)?.is_some() {
 87                  count += 1;
 88              }
 89          }
 90          Ok(count)
 91      }
 92      
 93      fn flush(&self) -> Result<(), StorageError> {
 94          self.db.flush()?;
 95          Ok(())
 96      }
 97  }
 98  
 99  #[cfg(test)]
100  mod tests {
101      use super::*;
102      use tempfile::tempdir;
103      
104      #[test]
105      fn test_sled_backend_crud() {
106          let dir = tempdir().unwrap();
107          let backend = SledBackend::open(dir.path()).unwrap();
108          
109          // Put and get
110          backend.put(b"key1", b"value1").unwrap();
111          assert_eq!(backend.get(b"key1").unwrap(), Some(b"value1".to_vec()));
112          
113          // Delete
114          assert!(backend.delete(b"key1").unwrap());
115          assert_eq!(backend.get(b"key1").unwrap(), None);
116          
117          // Delete non-existent
118          assert!(!backend.delete(b"key1").unwrap());
119      }
120      
121      #[test]
122      fn test_sled_backend_prefix_scan() {
123          let dir = tempdir().unwrap();
124          let backend = SledBackend::open(dir.path()).unwrap();
125          
126          backend.put(b"user1/chats/1", b"msg1").unwrap();
127          backend.put(b"user1/chats/2", b"msg2").unwrap();
128          backend.put(b"user1/contacts/a", b"alice").unwrap();
129          backend.put(b"user2/chats/1", b"other").unwrap();
130          
131          let results = backend.scan_prefix(b"user1/chats/").unwrap();
132          assert_eq!(results.len(), 2);
133      }
134      
135      #[test]
136      fn test_sled_backend_prefix_delete() {
137          let dir = tempdir().unwrap();
138          let backend = SledBackend::open(dir.path()).unwrap();
139          
140          backend.put(b"user1/chats/1", b"msg1").unwrap();
141          backend.put(b"user1/chats/2", b"msg2").unwrap();
142          backend.put(b"user2/chats/1", b"other").unwrap();
143          
144          let deleted = backend.delete_prefix(b"user1/").unwrap();
145          assert_eq!(deleted, 2);
146          
147          // user2's data untouched
148          assert_eq!(backend.get(b"user2/chats/1").unwrap(), Some(b"other".to_vec()));
149      }
150  }