...
This commit is contained in:
		
							
								
								
									
										287
									
								
								src/storage/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								src/storage/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,287 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    path::Path,
 | 
			
		||||
    sync::Arc,
 | 
			
		||||
    time::{SystemTime, UNIX_EPOCH},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use redb::{Database, TableDefinition};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::crypto::CryptoFactory;
 | 
			
		||||
use crate::error::DBError;
 | 
			
		||||
 | 
			
		||||
// Re-export modules
 | 
			
		||||
mod storage_basic;
 | 
			
		||||
mod storage_hset;
 | 
			
		||||
mod storage_lists;
 | 
			
		||||
mod storage_extra;
 | 
			
		||||
 | 
			
		||||
// Re-export implementations
 | 
			
		||||
// Note: These imports are used by the impl blocks in the submodules
 | 
			
		||||
// The compiler shows them as unused because they're not directly used in this file
 | 
			
		||||
// but they're needed for the Storage struct methods to be available
 | 
			
		||||
pub use storage_extra::*;
 | 
			
		||||
 | 
			
		||||
// Table definitions for different Redis data types
 | 
			
		||||
const TYPES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("types");
 | 
			
		||||
const STRINGS_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("strings");
 | 
			
		||||
const HASHES_TABLE: TableDefinition<(&str, &str), &[u8]> = TableDefinition::new("hashes");
 | 
			
		||||
const LISTS_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("lists");
 | 
			
		||||
const STREAMS_META_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("streams_meta");
 | 
			
		||||
const STREAMS_DATA_TABLE: TableDefinition<(&str, &str), &[u8]> = TableDefinition::new("streams_data");
 | 
			
		||||
const ENCRYPTED_TABLE: TableDefinition<&str, u8> = TableDefinition::new("encrypted");
 | 
			
		||||
const EXPIRATION_TABLE: TableDefinition<&str, u64> = TableDefinition::new("expiration");
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct StreamEntry {
 | 
			
		||||
    pub fields: Vec<(String, String)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct ListValue {
 | 
			
		||||
    pub elements: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
pub fn now_in_millis() -> u128 {
 | 
			
		||||
    let start = SystemTime::now();
 | 
			
		||||
    let duration_since_epoch = start.duration_since(UNIX_EPOCH).unwrap();
 | 
			
		||||
    duration_since_epoch.as_millis()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Storage {
 | 
			
		||||
    db: Database,
 | 
			
		||||
    crypto: Option<CryptoFactory>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Storage {
 | 
			
		||||
    pub fn new(path: impl AsRef<Path>, should_encrypt: bool, master_key: Option<&str>) -> Result<Self, DBError> {
 | 
			
		||||
        let db = Database::create(path)?;
 | 
			
		||||
        
 | 
			
		||||
        // Create tables if they don't exist
 | 
			
		||||
        let write_txn = db.begin_write()?;
 | 
			
		||||
        {
 | 
			
		||||
            let _ = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let _ = write_txn.open_table(STRINGS_TABLE)?;
 | 
			
		||||
            let _ = write_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
            let _ = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            let _ = write_txn.open_table(STREAMS_META_TABLE)?;
 | 
			
		||||
            let _ = write_txn.open_table(STREAMS_DATA_TABLE)?;
 | 
			
		||||
            let _ = write_txn.open_table(ENCRYPTED_TABLE)?;
 | 
			
		||||
            let _ = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
        }
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        
 | 
			
		||||
        // Check if database was previously encrypted
 | 
			
		||||
        let read_txn = db.begin_read()?;
 | 
			
		||||
        let encrypted_table = read_txn.open_table(ENCRYPTED_TABLE)?;
 | 
			
		||||
        let was_encrypted = encrypted_table.get("encrypted")?.map(|v| v.value() == 1).unwrap_or(false);
 | 
			
		||||
        drop(read_txn);
 | 
			
		||||
        
 | 
			
		||||
        let crypto = if should_encrypt || was_encrypted {
 | 
			
		||||
            if let Some(key) = master_key {
 | 
			
		||||
                Some(CryptoFactory::new(key.as_bytes()))
 | 
			
		||||
            } else {
 | 
			
		||||
                return Err(DBError("Encryption requested but no master key provided".to_string()));
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // If we're enabling encryption for the first time, mark it
 | 
			
		||||
        if should_encrypt && !was_encrypted {
 | 
			
		||||
            let write_txn = db.begin_write()?;
 | 
			
		||||
            {
 | 
			
		||||
                let mut encrypted_table = write_txn.open_table(ENCRYPTED_TABLE)?;
 | 
			
		||||
                encrypted_table.insert("encrypted", &1u8)?;
 | 
			
		||||
            }
 | 
			
		||||
            write_txn.commit()?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Ok(Storage {
 | 
			
		||||
            db,
 | 
			
		||||
            crypto,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn is_encrypted(&self) -> bool {
 | 
			
		||||
        self.crypto.is_some()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helper methods for encryption
 | 
			
		||||
    fn encrypt_if_needed(&self, data: &[u8]) -> Result<Vec<u8>, DBError> {
 | 
			
		||||
        if let Some(crypto) = &self.crypto {
 | 
			
		||||
            Ok(crypto.encrypt(data))
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(data.to_vec())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn decrypt_if_needed(&self, data: &[u8]) -> Result<Vec<u8>, DBError> {
 | 
			
		||||
        if let Some(crypto) = &self.crypto {
 | 
			
		||||
            Ok(crypto.decrypt(data)?)
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(data.to_vec())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use crate::storage_trait::StorageBackend;
 | 
			
		||||
 | 
			
		||||
impl StorageBackend for Storage {
 | 
			
		||||
    fn get(&self, key: &str) -> Result<Option<String>, DBError> {
 | 
			
		||||
        self.get(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set(&self, key: String, value: String) -> Result<(), DBError> {
 | 
			
		||||
        self.set(key, value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn setx(&self, key: String, value: String, expire_ms: u128) -> Result<(), DBError> {
 | 
			
		||||
        self.setx(key, value, expire_ms)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn del(&self, key: String) -> Result<(), DBError> {
 | 
			
		||||
        self.del(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn exists(&self, key: &str) -> Result<bool, DBError> {
 | 
			
		||||
        self.exists(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn keys(&self, pattern: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        self.keys(pattern)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn dbsize(&self) -> Result<i64, DBError> {
 | 
			
		||||
        self.dbsize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn flushdb(&self) -> Result<(), DBError> {
 | 
			
		||||
        self.flushdb()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_key_type(&self, key: &str) -> Result<Option<String>, DBError> {
 | 
			
		||||
        self.get_key_type(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn scan(&self, cursor: u64, pattern: Option<&str>, count: Option<u64>) -> Result<(u64, Vec<(String, String)>), DBError> {
 | 
			
		||||
        self.scan(cursor, pattern, count)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hscan(&self, key: &str, cursor: u64, pattern: Option<&str>, count: Option<u64>) -> Result<(u64, Vec<(String, String)>), DBError> {
 | 
			
		||||
        self.hscan(key, cursor, pattern, count)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hset(&self, key: &str, pairs: Vec<(String, String)>) -> Result<i64, DBError> {
 | 
			
		||||
        self.hset(key, pairs)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hget(&self, key: &str, field: &str) -> Result<Option<String>, DBError> {
 | 
			
		||||
        self.hget(key, field)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hgetall(&self, key: &str) -> Result<Vec<(String, String)>, DBError> {
 | 
			
		||||
        self.hgetall(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hdel(&self, key: &str, fields: Vec<String>) -> Result<i64, DBError> {
 | 
			
		||||
        self.hdel(key, fields)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hexists(&self, key: &str, field: &str) -> Result<bool, DBError> {
 | 
			
		||||
        self.hexists(key, field)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hkeys(&self, key: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        self.hkeys(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hvals(&self, key: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        self.hvals(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hlen(&self, key: &str) -> Result<i64, DBError> {
 | 
			
		||||
        self.hlen(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hmget(&self, key: &str, fields: Vec<String>) -> Result<Vec<Option<String>>, DBError> {
 | 
			
		||||
        self.hmget(key, fields)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn hsetnx(&self, key: &str, field: &str, value: &str) -> Result<bool, DBError> {
 | 
			
		||||
        self.hsetnx(key, field, value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn lpush(&self, key: &str, elements: Vec<String>) -> Result<i64, DBError> {
 | 
			
		||||
        self.lpush(key, elements)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn rpush(&self, key: &str, elements: Vec<String>) -> Result<i64, DBError> {
 | 
			
		||||
        self.rpush(key, elements)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn lpop(&self, key: &str, count: u64) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        self.lpop(key, count)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn rpop(&self, key: &str, count: u64) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        self.rpop(key, count)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn llen(&self, key: &str) -> Result<i64, DBError> {
 | 
			
		||||
        self.llen(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn lindex(&self, key: &str, index: i64) -> Result<Option<String>, DBError> {
 | 
			
		||||
        self.lindex(key, index)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn lrange(&self, key: &str, start: i64, stop: i64) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        self.lrange(key, start, stop)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ltrim(&self, key: &str, start: i64, stop: i64) -> Result<(), DBError> {
 | 
			
		||||
        self.ltrim(key, start, stop)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn lrem(&self, key: &str, count: i64, element: &str) -> Result<i64, DBError> {
 | 
			
		||||
        self.lrem(key, count, element)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ttl(&self, key: &str) -> Result<i64, DBError> {
 | 
			
		||||
        self.ttl(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn expire_seconds(&self, key: &str, secs: u64) -> Result<bool, DBError> {
 | 
			
		||||
        self.expire_seconds(key, secs)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn pexpire_millis(&self, key: &str, ms: u128) -> Result<bool, DBError> {
 | 
			
		||||
        self.pexpire_millis(key, ms)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn persist(&self, key: &str) -> Result<bool, DBError> {
 | 
			
		||||
        self.persist(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn expire_at_seconds(&self, key: &str, ts_secs: i64) -> Result<bool, DBError> {
 | 
			
		||||
        self.expire_at_seconds(key, ts_secs)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn pexpire_at_millis(&self, key: &str, ts_ms: i64) -> Result<bool, DBError> {
 | 
			
		||||
        self.pexpire_at_millis(key, ts_ms)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_encrypted(&self) -> bool {
 | 
			
		||||
        self.is_encrypted()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn info(&self) -> Result<Vec<(String, String)>, DBError> {
 | 
			
		||||
        self.info()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clone_arc(&self) -> Arc<dyn StorageBackend> {
 | 
			
		||||
        unimplemented!("Storage cloning not yet implemented for redb backend")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										245
									
								
								src/storage/storage_basic.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								src/storage/storage_basic.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
			
		||||
use redb::{ReadableTable};
 | 
			
		||||
use crate::error::DBError;
 | 
			
		||||
use super::*;
 | 
			
		||||
 | 
			
		||||
impl Storage {
 | 
			
		||||
    pub fn flushdb(&self) -> Result<(), DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let mut strings_table = write_txn.open_table(STRINGS_TABLE)?;
 | 
			
		||||
            let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            let mut streams_meta_table = write_txn.open_table(STREAMS_META_TABLE)?;
 | 
			
		||||
            let mut streams_data_table = write_txn.open_table(STREAMS_DATA_TABLE)?;
 | 
			
		||||
            let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
 | 
			
		||||
            // inefficient, but there is no other way
 | 
			
		||||
            let keys: Vec<String> = types_table.iter()?.map(|item| item.unwrap().0.value().to_string()).collect();
 | 
			
		||||
            for key in keys {
 | 
			
		||||
                types_table.remove(key.as_str())?;
 | 
			
		||||
            }
 | 
			
		||||
            let keys: Vec<String> = strings_table.iter()?.map(|item| item.unwrap().0.value().to_string()).collect();
 | 
			
		||||
            for key in keys {
 | 
			
		||||
                strings_table.remove(key.as_str())?;
 | 
			
		||||
            }
 | 
			
		||||
            let keys: Vec<(String, String)> = hashes_table
 | 
			
		||||
                .iter()?
 | 
			
		||||
                .map(|item| {
 | 
			
		||||
                    let binding = item.unwrap();
 | 
			
		||||
                    let (k, f) = binding.0.value();
 | 
			
		||||
                    (k.to_string(), f.to_string())
 | 
			
		||||
                })
 | 
			
		||||
                .collect();
 | 
			
		||||
            for (key, field) in keys {
 | 
			
		||||
                hashes_table.remove((key.as_str(), field.as_str()))?;
 | 
			
		||||
            }
 | 
			
		||||
            let keys: Vec<String> = lists_table.iter()?.map(|item| item.unwrap().0.value().to_string()).collect();
 | 
			
		||||
            for key in keys {
 | 
			
		||||
                lists_table.remove(key.as_str())?;
 | 
			
		||||
            }
 | 
			
		||||
            let keys: Vec<String> = streams_meta_table.iter()?.map(|item| item.unwrap().0.value().to_string()).collect();
 | 
			
		||||
            for key in keys {
 | 
			
		||||
                streams_meta_table.remove(key.as_str())?;
 | 
			
		||||
            }
 | 
			
		||||
            let keys: Vec<(String,String)> = streams_data_table.iter()?.map(|item| {
 | 
			
		||||
                let binding = item.unwrap();
 | 
			
		||||
                let (key, field) = binding.0.value();
 | 
			
		||||
                (key.to_string(), field.to_string())
 | 
			
		||||
            }).collect();
 | 
			
		||||
            for (key, field) in keys {
 | 
			
		||||
                streams_data_table.remove((key.as_str(), field.as_str()))?;
 | 
			
		||||
            }
 | 
			
		||||
            let keys: Vec<String> = expiration_table.iter()?.map(|item| item.unwrap().0.value().to_string()).collect();
 | 
			
		||||
            for key in keys {
 | 
			
		||||
                expiration_table.remove(key.as_str())?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_key_type(&self, key: &str) -> Result<Option<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        // Before returning type, check for expiration
 | 
			
		||||
        if let Some(type_val) = table.get(key)? {
 | 
			
		||||
            if type_val.value() == "string" {
 | 
			
		||||
                let expiration_table = read_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                if let Some(expires_at) = expiration_table.get(key)? {
 | 
			
		||||
                    if now_in_millis() > expires_at.value() as u128 {
 | 
			
		||||
                        // The key is expired, so it effectively has no type
 | 
			
		||||
                        return Ok(None);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Ok(Some(type_val.value().to_string()))
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(None)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Value is encrypted/decrypted
 | 
			
		||||
    pub fn get(&self, key: &str) -> Result<Option<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        match types_table.get(key)? {
 | 
			
		||||
            Some(type_val) if type_val.value() == "string" => {
 | 
			
		||||
                // Check expiration first (unencrypted)
 | 
			
		||||
                let expiration_table = read_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                if let Some(expires_at) = expiration_table.get(key)? {
 | 
			
		||||
                    if now_in_millis() > expires_at.value() as u128 {
 | 
			
		||||
                        drop(read_txn);
 | 
			
		||||
                        self.del(key.to_string())?;
 | 
			
		||||
                        return Ok(None);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Get and decrypt value
 | 
			
		||||
                let strings_table = read_txn.open_table(STRINGS_TABLE)?;
 | 
			
		||||
                match strings_table.get(key)? {
 | 
			
		||||
                    Some(data) => {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let value = String::from_utf8(decrypted)?;
 | 
			
		||||
                        Ok(Some(value))
 | 
			
		||||
                    }
 | 
			
		||||
                    None => Ok(None),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => Ok(None),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Value is encrypted before storage
 | 
			
		||||
    pub fn set(&self, key: String, value: String) -> Result<(), DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            types_table.insert(key.as_str(), "string")?;
 | 
			
		||||
            
 | 
			
		||||
            let mut strings_table = write_txn.open_table(STRINGS_TABLE)?;
 | 
			
		||||
            // Only encrypt the value, not expiration
 | 
			
		||||
            let encrypted = self.encrypt_if_needed(value.as_bytes())?;
 | 
			
		||||
            strings_table.insert(key.as_str(), encrypted.as_slice())?;
 | 
			
		||||
            
 | 
			
		||||
            // Remove any existing expiration since this is a regular SET
 | 
			
		||||
            let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
            expiration_table.remove(key.as_str())?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Value is encrypted before storage
 | 
			
		||||
    pub fn setx(&self, key: String, value: String, expire_ms: u128) -> Result<(), DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            types_table.insert(key.as_str(), "string")?;
 | 
			
		||||
            
 | 
			
		||||
            let mut strings_table = write_txn.open_table(STRINGS_TABLE)?;
 | 
			
		||||
            // Only encrypt the value
 | 
			
		||||
            let encrypted = self.encrypt_if_needed(value.as_bytes())?;
 | 
			
		||||
            strings_table.insert(key.as_str(), encrypted.as_slice())?;
 | 
			
		||||
            
 | 
			
		||||
            // Store expiration separately (unencrypted)
 | 
			
		||||
            let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
            let expires_at = expire_ms + now_in_millis();
 | 
			
		||||
            expiration_table.insert(key.as_str(), &(expires_at as u64))?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn del(&self, key: String) -> Result<(), DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let mut strings_table = write_txn.open_table(STRINGS_TABLE)?;
 | 
			
		||||
            let mut hashes_table: redb::Table<(&str, &str), &[u8]> = write_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            // Remove from type table
 | 
			
		||||
            types_table.remove(key.as_str())?;
 | 
			
		||||
            
 | 
			
		||||
            // Remove from strings table
 | 
			
		||||
            strings_table.remove(key.as_str())?;
 | 
			
		||||
            
 | 
			
		||||
            // Remove all hash fields for this key
 | 
			
		||||
            let mut to_remove = Vec::new();
 | 
			
		||||
            let mut iter = hashes_table.iter()?;
 | 
			
		||||
            while let Some(entry) = iter.next() {
 | 
			
		||||
                let entry = entry?;
 | 
			
		||||
                let (hash_key, field) = entry.0.value();
 | 
			
		||||
                if hash_key == key.as_str() {
 | 
			
		||||
                    to_remove.push((hash_key.to_string(), field.to_string()));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            drop(iter);
 | 
			
		||||
            
 | 
			
		||||
            for (hash_key, field) in to_remove {
 | 
			
		||||
                hashes_table.remove((hash_key.as_str(), field.as_str()))?;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Remove from lists table
 | 
			
		||||
            lists_table.remove(key.as_str())?;
 | 
			
		||||
            
 | 
			
		||||
            // Also remove expiration
 | 
			
		||||
            let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
            expiration_table.remove(key.as_str())?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn keys(&self, pattern: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        let mut keys = Vec::new();
 | 
			
		||||
        let mut iter = table.iter()?;
 | 
			
		||||
        while let Some(entry) = iter.next() {
 | 
			
		||||
            let key = entry?.0.value().to_string();
 | 
			
		||||
            if pattern == "*" || super::storage_extra::glob_match(pattern, &key) {
 | 
			
		||||
                keys.push(key);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Ok(keys)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Storage {
 | 
			
		||||
    pub fn dbsize(&self) -> Result<i64, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let expiration_table = read_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
 | 
			
		||||
        let mut count: i64 = 0;
 | 
			
		||||
        let mut iter = types_table.iter()?;
 | 
			
		||||
        while let Some(entry) = iter.next() {
 | 
			
		||||
            let entry = entry?;
 | 
			
		||||
            let key = entry.0.value();
 | 
			
		||||
            let ty = entry.1.value();
 | 
			
		||||
 | 
			
		||||
            if ty == "string" {
 | 
			
		||||
                if let Some(expires_at) = expiration_table.get(key)? {
 | 
			
		||||
                    if now_in_millis() > expires_at.value() as u128 {
 | 
			
		||||
                        // Skip logically expired string keys
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            count += 1;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(count)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										286
									
								
								src/storage/storage_extra.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								src/storage/storage_extra.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
			
		||||
use redb::{ReadableTable};
 | 
			
		||||
use crate::error::DBError;
 | 
			
		||||
use super::*;
 | 
			
		||||
 | 
			
		||||
impl Storage {
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Values are decrypted after retrieval
 | 
			
		||||
    pub fn scan(&self, cursor: u64, pattern: Option<&str>, count: Option<u64>) -> Result<(u64, Vec<(String, String)>), DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let strings_table = read_txn.open_table(STRINGS_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        let mut result = Vec::new();
 | 
			
		||||
        let mut current_cursor = 0u64;
 | 
			
		||||
        let limit = count.unwrap_or(10) as usize;
 | 
			
		||||
        
 | 
			
		||||
        let mut iter = types_table.iter()?;
 | 
			
		||||
        while let Some(entry) = iter.next() {
 | 
			
		||||
            let entry = entry?;
 | 
			
		||||
            let key = entry.0.value().to_string();
 | 
			
		||||
            let key_type = entry.1.value().to_string();
 | 
			
		||||
            
 | 
			
		||||
            if current_cursor >= cursor {
 | 
			
		||||
                // Apply pattern matching if specified
 | 
			
		||||
                let matches = if let Some(pat) = pattern {
 | 
			
		||||
                    glob_match(pat, &key)
 | 
			
		||||
                } else {
 | 
			
		||||
                    true
 | 
			
		||||
                };
 | 
			
		||||
                
 | 
			
		||||
                if matches {
 | 
			
		||||
                    // For scan, we return key-value pairs for string types
 | 
			
		||||
                    if key_type == "string" {
 | 
			
		||||
                        if let Some(data) = strings_table.get(key.as_str())? {
 | 
			
		||||
                            let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                            let value = String::from_utf8(decrypted)?;
 | 
			
		||||
                            result.push((key, value));
 | 
			
		||||
                        } else {
 | 
			
		||||
                            result.push((key, String::new()));
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // For non-string types, just return the key with type as value
 | 
			
		||||
                        result.push((key, key_type));
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    if result.len() >= limit {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            current_cursor += 1;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let next_cursor = if result.len() < limit { 0 } else { current_cursor };
 | 
			
		||||
        Ok((next_cursor, result))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn ttl(&self, key: &str) -> Result<i64, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        match types_table.get(key)? {
 | 
			
		||||
            Some(type_val) if type_val.value() == "string" => {
 | 
			
		||||
                let expiration_table = read_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                match expiration_table.get(key)? {
 | 
			
		||||
                    Some(expires_at) => {
 | 
			
		||||
                        let now = now_in_millis();
 | 
			
		||||
                        let expires_at_ms = expires_at.value() as u128;
 | 
			
		||||
                        if now >= expires_at_ms {
 | 
			
		||||
                            Ok(-2) // Key has expired
 | 
			
		||||
                        } else {
 | 
			
		||||
                            Ok(((expires_at_ms - now) / 1000) as i64) // TTL in seconds
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    None => Ok(-1), // Key exists but has no expiration
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Ok(-1), // Key exists but is not a string (no expiration support for other types)
 | 
			
		||||
            None => Ok(-2), // Key does not exist
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn exists(&self, key: &str) -> Result<bool, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        match types_table.get(key)? {
 | 
			
		||||
            Some(type_val) if type_val.value() == "string" => {
 | 
			
		||||
                // Check if string key has expired
 | 
			
		||||
                let expiration_table = read_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                if let Some(expires_at) = expiration_table.get(key)? {
 | 
			
		||||
                    if now_in_millis() > expires_at.value() as u128 {
 | 
			
		||||
                        return Ok(false); // Key has expired
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Ok(true)
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Ok(true), // Key exists and is not a string
 | 
			
		||||
            None => Ok(false), // Key does not exist
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // -------- Expiration helpers (string keys only, consistent with TTL/EXISTS) --------
 | 
			
		||||
 | 
			
		||||
    // Set expiry in seconds; returns true if applied (key exists and is string), false otherwise
 | 
			
		||||
    pub fn expire_seconds(&self, key: &str, secs: u64) -> Result<bool, DBError> {
 | 
			
		||||
        // Determine eligibility first to avoid holding borrows across commit
 | 
			
		||||
        let mut applied = false;
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let is_string = types_table
 | 
			
		||||
                .get(key)?
 | 
			
		||||
                .map(|v| v.value() == "string")
 | 
			
		||||
                .unwrap_or(false);
 | 
			
		||||
            if is_string {
 | 
			
		||||
                let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                let expires_at = now_in_millis() + (secs as u128) * 1000;
 | 
			
		||||
                expiration_table.insert(key, &(expires_at as u64))?;
 | 
			
		||||
                applied = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(applied)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set expiry in milliseconds; returns true if applied (key exists and is string), false otherwise
 | 
			
		||||
    pub fn pexpire_millis(&self, key: &str, ms: u128) -> Result<bool, DBError> {
 | 
			
		||||
        let mut applied = false;
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let is_string = types_table
 | 
			
		||||
                .get(key)?
 | 
			
		||||
                .map(|v| v.value() == "string")
 | 
			
		||||
                .unwrap_or(false);
 | 
			
		||||
            if is_string {
 | 
			
		||||
                let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                let expires_at = now_in_millis() + ms;
 | 
			
		||||
                expiration_table.insert(key, &(expires_at as u64))?;
 | 
			
		||||
                applied = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(applied)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove expiry if present; returns true if removed, false otherwise
 | 
			
		||||
    pub fn persist(&self, key: &str) -> Result<bool, DBError> {
 | 
			
		||||
        let mut removed = false;
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let is_string = types_table
 | 
			
		||||
                .get(key)?
 | 
			
		||||
                .map(|v| v.value() == "string")
 | 
			
		||||
                .unwrap_or(false);
 | 
			
		||||
            if is_string {
 | 
			
		||||
                let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                if expiration_table.remove(key)?.is_some() {
 | 
			
		||||
                    removed = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(removed)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Absolute EXPIREAT in seconds since epoch
 | 
			
		||||
    // Returns true if applied (key exists and is string), false otherwise
 | 
			
		||||
    pub fn expire_at_seconds(&self, key: &str, ts_secs: i64) -> Result<bool, DBError> {
 | 
			
		||||
        let mut applied = false;
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let is_string = types_table
 | 
			
		||||
                .get(key)?
 | 
			
		||||
                .map(|v| v.value() == "string")
 | 
			
		||||
                .unwrap_or(false);
 | 
			
		||||
            if is_string {
 | 
			
		||||
                let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                let expires_at_ms: u128 = if ts_secs <= 0 { 0 } else { (ts_secs as u128) * 1000 };
 | 
			
		||||
                expiration_table.insert(key, &((expires_at_ms as u64)))?;
 | 
			
		||||
                applied = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(applied)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Absolute PEXPIREAT in milliseconds since epoch
 | 
			
		||||
    // Returns true if applied (key exists and is string), false otherwise
 | 
			
		||||
    pub fn pexpire_at_millis(&self, key: &str, ts_ms: i64) -> Result<bool, DBError> {
 | 
			
		||||
        let mut applied = false;
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let is_string = types_table
 | 
			
		||||
                .get(key)?
 | 
			
		||||
                .map(|v| v.value() == "string")
 | 
			
		||||
                .unwrap_or(false);
 | 
			
		||||
            if is_string {
 | 
			
		||||
                let mut expiration_table = write_txn.open_table(EXPIRATION_TABLE)?;
 | 
			
		||||
                let expires_at_ms: u128 = if ts_ms <= 0 { 0 } else { ts_ms as u128 };
 | 
			
		||||
                expiration_table.insert(key, &((expires_at_ms as u64)))?;
 | 
			
		||||
                applied = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(applied)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn info(&self) -> Result<Vec<(String, String)>, DBError> {
 | 
			
		||||
        let dbsize = self.dbsize()?;
 | 
			
		||||
        Ok(vec![
 | 
			
		||||
            ("db_size".to_string(), dbsize.to_string()),
 | 
			
		||||
            ("is_encrypted".to_string(), self.is_encrypted().to_string()),
 | 
			
		||||
        ])
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Utility function for glob pattern matching
 | 
			
		||||
pub fn glob_match(pattern: &str, text: &str) -> bool {
 | 
			
		||||
    if pattern == "*" {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Simple glob matching - supports * and ? wildcards
 | 
			
		||||
    let pattern_chars: Vec<char> = pattern.chars().collect();
 | 
			
		||||
    let text_chars: Vec<char> = text.chars().collect();
 | 
			
		||||
    
 | 
			
		||||
    fn match_recursive(pattern: &[char], text: &[char], pi: usize, ti: usize) -> bool {
 | 
			
		||||
        if pi >= pattern.len() {
 | 
			
		||||
            return ti >= text.len();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ti >= text.len() {
 | 
			
		||||
            // Check if remaining pattern is all '*'
 | 
			
		||||
            return pattern[pi..].iter().all(|&c| c == '*');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        match pattern[pi] {
 | 
			
		||||
            '*' => {
 | 
			
		||||
                // Try matching zero or more characters
 | 
			
		||||
                for i in ti..=text.len() {
 | 
			
		||||
                    if match_recursive(pattern, text, pi + 1, i) {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                false
 | 
			
		||||
            }
 | 
			
		||||
            '?' => {
 | 
			
		||||
                // Match exactly one character
 | 
			
		||||
                match_recursive(pattern, text, pi + 1, ti + 1)
 | 
			
		||||
            }
 | 
			
		||||
            c => {
 | 
			
		||||
                // Match exact character
 | 
			
		||||
                if text[ti] == c {
 | 
			
		||||
                    match_recursive(pattern, text, pi + 1, ti + 1)
 | 
			
		||||
                } else {
 | 
			
		||||
                    false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    match_recursive(&pattern_chars, &text_chars, 0, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_glob_match() {
 | 
			
		||||
        assert!(glob_match("*", "anything"));
 | 
			
		||||
        assert!(glob_match("hello", "hello"));
 | 
			
		||||
        assert!(!glob_match("hello", "world"));
 | 
			
		||||
        assert!(glob_match("h*o", "hello"));
 | 
			
		||||
        assert!(glob_match("h*o", "ho"));
 | 
			
		||||
        assert!(!glob_match("h*o", "hi"));
 | 
			
		||||
        assert!(glob_match("h?llo", "hello"));
 | 
			
		||||
        assert!(!glob_match("h?llo", "hllo"));
 | 
			
		||||
        assert!(glob_match("*test*", "this_is_a_test_string"));
 | 
			
		||||
        assert!(!glob_match("*test*", "this_is_a_string"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										377
									
								
								src/storage/storage_hset.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										377
									
								
								src/storage/storage_hset.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,377 @@
 | 
			
		||||
use redb::{ReadableTable};
 | 
			
		||||
use crate::error::DBError;
 | 
			
		||||
use super::*;
 | 
			
		||||
 | 
			
		||||
impl Storage {
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Values are encrypted before storage
 | 
			
		||||
    pub fn hset(&self, key: &str, pairs: Vec<(String, String)>) -> Result<i64, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut new_fields = 0i64;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            let key_type = {
 | 
			
		||||
                let access_guard = types_table.get(key)?;
 | 
			
		||||
                access_guard.map(|v| v.value().to_string())
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            match key_type.as_deref() {
 | 
			
		||||
                Some("hash") | None => { // Proceed if hash or new key
 | 
			
		||||
                    // Set the type to hash (only if new key or existing hash)
 | 
			
		||||
                    types_table.insert(key, "hash")?;
 | 
			
		||||
                    
 | 
			
		||||
                    for (field, value) in pairs {
 | 
			
		||||
                        // Check if field already exists
 | 
			
		||||
                        let exists = hashes_table.get((key, field.as_str()))?.is_some();
 | 
			
		||||
                        
 | 
			
		||||
                        // Encrypt the value before storing
 | 
			
		||||
                        let encrypted = self.encrypt_if_needed(value.as_bytes())?;
 | 
			
		||||
                        hashes_table.insert((key, field.as_str()), encrypted.as_slice())?;
 | 
			
		||||
                        
 | 
			
		||||
                        if !exists {
 | 
			
		||||
                            new_fields += 1;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Some(_) => return Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(new_fields)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Value is decrypted after retrieval
 | 
			
		||||
    pub fn hget(&self, key: &str, field: &str) -> Result<Option<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        let key_type = types_table.get(key)?.map(|v| v.value().to_string());
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                match hashes_table.get((key, field))? {
 | 
			
		||||
                    Some(data) => {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let value = String::from_utf8(decrypted)?;
 | 
			
		||||
                        Ok(Some(value))
 | 
			
		||||
                    }
 | 
			
		||||
                    None => Ok(None),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok(None),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: All values are decrypted after retrieval
 | 
			
		||||
    pub fn hgetall(&self, key: &str) -> Result<Vec<(String, String)>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                let mut result = Vec::new();
 | 
			
		||||
                
 | 
			
		||||
                let mut iter = hashes_table.iter()?;
 | 
			
		||||
                while let Some(entry) = iter.next() {
 | 
			
		||||
                    let entry = entry?;
 | 
			
		||||
                    let (hash_key, field) = entry.0.value();
 | 
			
		||||
                    if hash_key == key {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(entry.1.value())?;
 | 
			
		||||
                        let value = String::from_utf8(decrypted)?;
 | 
			
		||||
                        result.push((field.to_string(), value));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Ok(result)
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok(Vec::new()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn hdel(&self, key: &str, fields: Vec<String>) -> Result<i64, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut deleted = 0i64;
 | 
			
		||||
        
 | 
			
		||||
        // First check if key exists and is a hash
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                
 | 
			
		||||
                for field in fields {
 | 
			
		||||
                    if hashes_table.remove((key, field.as_str()))?.is_some() {
 | 
			
		||||
                        deleted += 1;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Check if hash is now empty and remove type if so
 | 
			
		||||
                let mut has_fields = false;
 | 
			
		||||
                let mut iter = hashes_table.iter()?;
 | 
			
		||||
                while let Some(entry) = iter.next() {
 | 
			
		||||
                    let entry = entry?;
 | 
			
		||||
                    let (hash_key, _) = entry.0.value();
 | 
			
		||||
                    if hash_key == key {
 | 
			
		||||
                        has_fields = true;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                drop(iter);
 | 
			
		||||
                
 | 
			
		||||
                if !has_fields {
 | 
			
		||||
                    let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
                    types_table.remove(key)?;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => return Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => {} // Key does not exist, nothing to delete, return 0 deleted
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(deleted)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn hexists(&self, key: &str, field: &str) -> Result<bool, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                Ok(hashes_table.get((key, field))?.is_some())
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok(false),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn hkeys(&self, key: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                let mut result = Vec::new();
 | 
			
		||||
                
 | 
			
		||||
                let mut iter = hashes_table.iter()?;
 | 
			
		||||
                while let Some(entry) = iter.next() {
 | 
			
		||||
                    let entry = entry?;
 | 
			
		||||
                    let (hash_key, field) = entry.0.value();
 | 
			
		||||
                    if hash_key == key {
 | 
			
		||||
                        result.push(field.to_string());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Ok(result)
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok(Vec::new()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: All values are decrypted after retrieval
 | 
			
		||||
    pub fn hvals(&self, key: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                let mut result = Vec::new();
 | 
			
		||||
                
 | 
			
		||||
                let mut iter = hashes_table.iter()?;
 | 
			
		||||
                while let Some(entry) = iter.next() {
 | 
			
		||||
                    let entry = entry?;
 | 
			
		||||
                    let (hash_key, _) = entry.0.value();
 | 
			
		||||
                    if hash_key == key {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(entry.1.value())?;
 | 
			
		||||
                        let value = String::from_utf8(decrypted)?;
 | 
			
		||||
                        result.push(value);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Ok(result)
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok(Vec::new()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn hlen(&self, key: &str) -> Result<i64, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                let mut count = 0i64;
 | 
			
		||||
                
 | 
			
		||||
                let mut iter = hashes_table.iter()?;
 | 
			
		||||
                while let Some(entry) = iter.next() {
 | 
			
		||||
                    let entry = entry?;
 | 
			
		||||
                    let (hash_key, _) = entry.0.value();
 | 
			
		||||
                    if hash_key == key {
 | 
			
		||||
                        count += 1;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Ok(count)
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok(0),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Values are decrypted after retrieval
 | 
			
		||||
    pub fn hmget(&self, key: &str, fields: Vec<String>) -> Result<Vec<Option<String>>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                let mut result = Vec::new();
 | 
			
		||||
                
 | 
			
		||||
                for field in fields {
 | 
			
		||||
                    match hashes_table.get((key, field.as_str()))? {
 | 
			
		||||
                        Some(data) => {
 | 
			
		||||
                            let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                            let value = String::from_utf8(decrypted)?;
 | 
			
		||||
                            result.push(Some(value));
 | 
			
		||||
                        }
 | 
			
		||||
                        None => result.push(None),
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Ok(result)
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok(fields.into_iter().map(|_| None).collect()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Value is encrypted before storage
 | 
			
		||||
    pub fn hsetnx(&self, key: &str, field: &str, value: &str) -> Result<bool, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut result = false;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            let key_type = {
 | 
			
		||||
                let access_guard = types_table.get(key)?;
 | 
			
		||||
                access_guard.map(|v| v.value().to_string())
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            match key_type.as_deref() {
 | 
			
		||||
                Some("hash") | None => { // Proceed if hash or new key
 | 
			
		||||
                    // Check if field already exists
 | 
			
		||||
                    if hashes_table.get((key, field))?.is_none() {
 | 
			
		||||
                        // Set the type to hash (only if new key or existing hash)
 | 
			
		||||
                        types_table.insert(key, "hash")?;
 | 
			
		||||
                        
 | 
			
		||||
                        // Encrypt the value before storing
 | 
			
		||||
                        let encrypted = self.encrypt_if_needed(value.as_bytes())?;
 | 
			
		||||
                        hashes_table.insert((key, field), encrypted.as_slice())?;
 | 
			
		||||
                        result = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Some(_) => return Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Values are decrypted after retrieval
 | 
			
		||||
    pub fn hscan(&self, key: &str, cursor: u64, pattern: Option<&str>, count: Option<u64>) -> Result<(u64, Vec<(String, String)>), DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        let key_type = {
 | 
			
		||||
            let access_guard = types_table.get(key)?;
 | 
			
		||||
            access_guard.map(|v| v.value().to_string())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match key_type.as_deref() {
 | 
			
		||||
            Some("hash") => {
 | 
			
		||||
                let hashes_table = read_txn.open_table(HASHES_TABLE)?;
 | 
			
		||||
                let mut result = Vec::new();
 | 
			
		||||
                let mut current_cursor = 0u64;
 | 
			
		||||
                let limit = count.unwrap_or(10) as usize;
 | 
			
		||||
                
 | 
			
		||||
                let mut iter = hashes_table.iter()?;
 | 
			
		||||
                while let Some(entry) = iter.next() {
 | 
			
		||||
                    let entry = entry?;
 | 
			
		||||
                    let (hash_key, field) = entry.0.value();
 | 
			
		||||
                    
 | 
			
		||||
                    if hash_key == key {
 | 
			
		||||
                        if current_cursor >= cursor {
 | 
			
		||||
                            let field_str = field.to_string();
 | 
			
		||||
                            
 | 
			
		||||
                            // Apply pattern matching if specified
 | 
			
		||||
                            let matches = if let Some(pat) = pattern {
 | 
			
		||||
                                super::storage_extra::glob_match(pat, &field_str)
 | 
			
		||||
                            } else {
 | 
			
		||||
                                true
 | 
			
		||||
                            };
 | 
			
		||||
                            
 | 
			
		||||
                            if matches {
 | 
			
		||||
                                let decrypted = self.decrypt_if_needed(entry.1.value())?;
 | 
			
		||||
                                let value = String::from_utf8(decrypted)?;
 | 
			
		||||
                                result.push((field_str, value));
 | 
			
		||||
                                
 | 
			
		||||
                                if result.len() >= limit {
 | 
			
		||||
                                    break;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        current_cursor += 1;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                let next_cursor = if result.len() < limit { 0 } else { current_cursor };
 | 
			
		||||
                Ok((next_cursor, result))
 | 
			
		||||
            }
 | 
			
		||||
            Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
 | 
			
		||||
            None => Ok((0, Vec::new())),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										403
									
								
								src/storage/storage_lists.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										403
									
								
								src/storage/storage_lists.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,403 @@
 | 
			
		||||
use redb::{ReadableTable};
 | 
			
		||||
use crate::error::DBError;
 | 
			
		||||
use super::*;
 | 
			
		||||
 | 
			
		||||
impl Storage {
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Elements are encrypted before storage
 | 
			
		||||
    pub fn lpush(&self, key: &str, elements: Vec<String>) -> Result<i64, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut _length = 0i64;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            // Set the type to list
 | 
			
		||||
            types_table.insert(key, "list")?;
 | 
			
		||||
            
 | 
			
		||||
            // Get current list or create empty one
 | 
			
		||||
            let mut list: Vec<String> = match lists_table.get(key)? {
 | 
			
		||||
                Some(data) => {
 | 
			
		||||
                    let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                    serde_json::from_slice(&decrypted)?
 | 
			
		||||
                }
 | 
			
		||||
                None => Vec::new(),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            // Add elements to the front (left)
 | 
			
		||||
            for element in elements.into_iter() {
 | 
			
		||||
                list.insert(0, element);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            _length = list.len() as i64;
 | 
			
		||||
            
 | 
			
		||||
            // Encrypt and store the updated list
 | 
			
		||||
            let serialized = serde_json::to_vec(&list)?;
 | 
			
		||||
            let encrypted = self.encrypt_if_needed(&serialized)?;
 | 
			
		||||
            lists_table.insert(key, encrypted.as_slice())?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(_length)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Elements are encrypted before storage
 | 
			
		||||
    pub fn rpush(&self, key: &str, elements: Vec<String>) -> Result<i64, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut _length = 0i64;
 | 
			
		||||
        
 | 
			
		||||
        {
 | 
			
		||||
            let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            // Set the type to list
 | 
			
		||||
            types_table.insert(key, "list")?;
 | 
			
		||||
            
 | 
			
		||||
            // Get current list or create empty one
 | 
			
		||||
            let mut list: Vec<String> = match lists_table.get(key)? {
 | 
			
		||||
                Some(data) => {
 | 
			
		||||
                    let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                    serde_json::from_slice(&decrypted)?
 | 
			
		||||
                }
 | 
			
		||||
                None => Vec::new(),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            // Add elements to the end (right)
 | 
			
		||||
            list.extend(elements);
 | 
			
		||||
            _length = list.len() as i64;
 | 
			
		||||
            
 | 
			
		||||
            // Encrypt and store the updated list
 | 
			
		||||
            let serialized = serde_json::to_vec(&list)?;
 | 
			
		||||
            let encrypted = self.encrypt_if_needed(&serialized)?;
 | 
			
		||||
            lists_table.insert(key, encrypted.as_slice())?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(_length)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Elements are decrypted after retrieval and encrypted before storage
 | 
			
		||||
    pub fn lpop(&self, key: &str, count: u64) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut result = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
        // First check if key exists and is a list, and get the data
 | 
			
		||||
        let list_data = {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            let result = match types_table.get(key)? {
 | 
			
		||||
                Some(type_val) if type_val.value() == "list" => {
 | 
			
		||||
                    if let Some(data) = lists_table.get(key)? {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let list: Vec<String> = serde_json::from_slice(&decrypted)?;
 | 
			
		||||
                        Some(list)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        None
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _ => None,
 | 
			
		||||
            };
 | 
			
		||||
            result
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        if let Some(mut list) = list_data {
 | 
			
		||||
            let pop_count = std::cmp::min(count as usize, list.len());
 | 
			
		||||
            for _ in 0..pop_count {
 | 
			
		||||
                if !list.is_empty() {
 | 
			
		||||
                    result.push(list.remove(0));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            if list.is_empty() {
 | 
			
		||||
                // Remove the key if list is empty
 | 
			
		||||
                lists_table.remove(key)?;
 | 
			
		||||
                let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
                types_table.remove(key)?;
 | 
			
		||||
            } else {
 | 
			
		||||
                // Encrypt and store the updated list
 | 
			
		||||
                let serialized = serde_json::to_vec(&list)?;
 | 
			
		||||
                let encrypted = self.encrypt_if_needed(&serialized)?;
 | 
			
		||||
                lists_table.insert(key, encrypted.as_slice())?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Elements are decrypted after retrieval and encrypted before storage
 | 
			
		||||
    pub fn rpop(&self, key: &str, count: u64) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut result = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
        // First check if key exists and is a list, and get the data
 | 
			
		||||
        let list_data = {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            let result = match types_table.get(key)? {
 | 
			
		||||
                Some(type_val) if type_val.value() == "list" => {
 | 
			
		||||
                    if let Some(data) = lists_table.get(key)? {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let list: Vec<String> = serde_json::from_slice(&decrypted)?;
 | 
			
		||||
                        Some(list)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        None
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _ => None,
 | 
			
		||||
            };
 | 
			
		||||
            result
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        if let Some(mut list) = list_data {
 | 
			
		||||
            let pop_count = std::cmp::min(count as usize, list.len());
 | 
			
		||||
            for _ in 0..pop_count {
 | 
			
		||||
                if !list.is_empty() {
 | 
			
		||||
                    result.push(list.pop().unwrap());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            if list.is_empty() {
 | 
			
		||||
                // Remove the key if list is empty
 | 
			
		||||
                lists_table.remove(key)?;
 | 
			
		||||
                let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
                types_table.remove(key)?;
 | 
			
		||||
            } else {
 | 
			
		||||
                // Encrypt and store the updated list
 | 
			
		||||
                let serialized = serde_json::to_vec(&list)?;
 | 
			
		||||
                let encrypted = self.encrypt_if_needed(&serialized)?;
 | 
			
		||||
                lists_table.insert(key, encrypted.as_slice())?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn llen(&self, key: &str) -> Result<i64, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        match types_table.get(key)? {
 | 
			
		||||
            Some(type_val) if type_val.value() == "list" => {
 | 
			
		||||
                let lists_table = read_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
                match lists_table.get(key)? {
 | 
			
		||||
                    Some(data) => {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let list: Vec<String> = serde_json::from_slice(&decrypted)?;
 | 
			
		||||
                        Ok(list.len() as i64)
 | 
			
		||||
                    }
 | 
			
		||||
                    None => Ok(0),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => Ok(0),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Element is decrypted after retrieval
 | 
			
		||||
    pub fn lindex(&self, key: &str, index: i64) -> Result<Option<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        match types_table.get(key)? {
 | 
			
		||||
            Some(type_val) if type_val.value() == "list" => {
 | 
			
		||||
                let lists_table = read_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
                match lists_table.get(key)? {
 | 
			
		||||
                    Some(data) => {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let list: Vec<String> = serde_json::from_slice(&decrypted)?;
 | 
			
		||||
                        
 | 
			
		||||
                        let actual_index = if index < 0 {
 | 
			
		||||
                            list.len() as i64 + index
 | 
			
		||||
                        } else {
 | 
			
		||||
                            index
 | 
			
		||||
                        };
 | 
			
		||||
                        
 | 
			
		||||
                        if actual_index >= 0 && (actual_index as usize) < list.len() {
 | 
			
		||||
                            Ok(Some(list[actual_index as usize].clone()))
 | 
			
		||||
                        } else {
 | 
			
		||||
                            Ok(None)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    None => Ok(None),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => Ok(None),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Elements are decrypted after retrieval
 | 
			
		||||
    pub fn lrange(&self, key: &str, start: i64, stop: i64) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        let read_txn = self.db.begin_read()?;
 | 
			
		||||
        let types_table = read_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
        
 | 
			
		||||
        match types_table.get(key)? {
 | 
			
		||||
            Some(type_val) if type_val.value() == "list" => {
 | 
			
		||||
                let lists_table = read_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
                match lists_table.get(key)? {
 | 
			
		||||
                    Some(data) => {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let list: Vec<String> = serde_json::from_slice(&decrypted)?;
 | 
			
		||||
                        
 | 
			
		||||
                        if list.is_empty() {
 | 
			
		||||
                            return Ok(Vec::new());
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        let len = list.len() as i64;
 | 
			
		||||
                        let start_idx = if start < 0 { std::cmp::max(0, len + start) } else { std::cmp::min(start, len) };
 | 
			
		||||
                        let stop_idx = if stop < 0 { std::cmp::max(-1, len + stop) } else { std::cmp::min(stop, len - 1) };
 | 
			
		||||
                        
 | 
			
		||||
                        if start_idx > stop_idx || start_idx >= len {
 | 
			
		||||
                            return Ok(Vec::new());
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        let start_usize = start_idx as usize;
 | 
			
		||||
                        let stop_usize = (stop_idx + 1) as usize;
 | 
			
		||||
                        
 | 
			
		||||
                        Ok(list[start_usize..std::cmp::min(stop_usize, list.len())].to_vec())
 | 
			
		||||
                    }
 | 
			
		||||
                    None => Ok(Vec::new()),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => Ok(Vec::new()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Elements are decrypted after retrieval and encrypted before storage
 | 
			
		||||
    pub fn ltrim(&self, key: &str, start: i64, stop: i64) -> Result<(), DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        
 | 
			
		||||
        // First check if key exists and is a list, and get the data
 | 
			
		||||
        let list_data = {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            let result = match types_table.get(key)? {
 | 
			
		||||
                Some(type_val) if type_val.value() == "list" => {
 | 
			
		||||
                    if let Some(data) = lists_table.get(key)? {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let list: Vec<String> = serde_json::from_slice(&decrypted)?;
 | 
			
		||||
                        Some(list)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        None
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _ => None,
 | 
			
		||||
            };
 | 
			
		||||
            result
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        if let Some(list) = list_data {
 | 
			
		||||
            if list.is_empty() {
 | 
			
		||||
                write_txn.commit()?;
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            let len = list.len() as i64;
 | 
			
		||||
            let start_idx = if start < 0 { std::cmp::max(0, len + start) } else { std::cmp::min(start, len) };
 | 
			
		||||
            let stop_idx = if stop < 0 { std::cmp::max(-1, len + stop) } else { std::cmp::min(stop, len - 1) };
 | 
			
		||||
            
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            if start_idx > stop_idx || start_idx >= len {
 | 
			
		||||
                // Remove the entire list
 | 
			
		||||
                lists_table.remove(key)?;
 | 
			
		||||
                let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
                types_table.remove(key)?;
 | 
			
		||||
            } else {
 | 
			
		||||
                let start_usize = start_idx as usize;
 | 
			
		||||
                let stop_usize = (stop_idx + 1) as usize;
 | 
			
		||||
                let trimmed = list[start_usize..std::cmp::min(stop_usize, list.len())].to_vec();
 | 
			
		||||
                
 | 
			
		||||
                if trimmed.is_empty() {
 | 
			
		||||
                    lists_table.remove(key)?;
 | 
			
		||||
                    let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
                    types_table.remove(key)?;
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Encrypt and store the trimmed list
 | 
			
		||||
                    let serialized = serde_json::to_vec(&trimmed)?;
 | 
			
		||||
                    let encrypted = self.encrypt_if_needed(&serialized)?;
 | 
			
		||||
                    lists_table.insert(key, encrypted.as_slice())?;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ✅ ENCRYPTION APPLIED: Elements are decrypted after retrieval and encrypted before storage
 | 
			
		||||
    pub fn lrem(&self, key: &str, count: i64, element: &str) -> Result<i64, DBError> {
 | 
			
		||||
        let write_txn = self.db.begin_write()?;
 | 
			
		||||
        let mut removed = 0i64;
 | 
			
		||||
        
 | 
			
		||||
        // First check if key exists and is a list, and get the data
 | 
			
		||||
        let list_data = {
 | 
			
		||||
            let types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
            let lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            
 | 
			
		||||
            let result = match types_table.get(key)? {
 | 
			
		||||
                Some(type_val) if type_val.value() == "list" => {
 | 
			
		||||
                    if let Some(data) = lists_table.get(key)? {
 | 
			
		||||
                        let decrypted = self.decrypt_if_needed(data.value())?;
 | 
			
		||||
                        let list: Vec<String> = serde_json::from_slice(&decrypted)?;
 | 
			
		||||
                        Some(list)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        None
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _ => None,
 | 
			
		||||
            };
 | 
			
		||||
            result
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        if let Some(mut list) = list_data {
 | 
			
		||||
            if count == 0 {
 | 
			
		||||
                // Remove all occurrences
 | 
			
		||||
                let original_len = list.len();
 | 
			
		||||
                list.retain(|x| x != element);
 | 
			
		||||
                removed = (original_len - list.len()) as i64;
 | 
			
		||||
            } else if count > 0 {
 | 
			
		||||
                // Remove first count occurrences
 | 
			
		||||
                let mut to_remove = count as usize;
 | 
			
		||||
                list.retain(|x| {
 | 
			
		||||
                    if x == element && to_remove > 0 {
 | 
			
		||||
                        to_remove -= 1;
 | 
			
		||||
                        removed += 1;
 | 
			
		||||
                        false
 | 
			
		||||
                    } else {
 | 
			
		||||
                        true
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                // Remove last |count| occurrences
 | 
			
		||||
                let mut to_remove = (-count) as usize;
 | 
			
		||||
                for i in (0..list.len()).rev() {
 | 
			
		||||
                    if list[i] == element && to_remove > 0 {
 | 
			
		||||
                        list.remove(i);
 | 
			
		||||
                        to_remove -= 1;
 | 
			
		||||
                        removed += 1;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
 | 
			
		||||
            if list.is_empty() {
 | 
			
		||||
                lists_table.remove(key)?;
 | 
			
		||||
                let mut types_table = write_txn.open_table(TYPES_TABLE)?;
 | 
			
		||||
                types_table.remove(key)?;
 | 
			
		||||
            } else {
 | 
			
		||||
                // Encrypt and store the updated list
 | 
			
		||||
                let serialized = serde_json::to_vec(&list)?;
 | 
			
		||||
                let encrypted = self.encrypt_if_needed(&serialized)?;
 | 
			
		||||
                lists_table.insert(key, encrypted.as_slice())?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        write_txn.commit()?;
 | 
			
		||||
        Ok(removed)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user