From f8dd304820cbc6b33f45bc80d72dc1d22d78da59 Mon Sep 17 00:00:00 2001 From: despiegk Date: Sat, 16 Aug 2025 10:41:26 +0200 Subject: [PATCH] it works --- src/cmd.rs | 36 ++++----- src/server.rs | 7 ++ src/storage.rs | 193 ++++++++++++++++++++----------------------------- 3 files changed, 99 insertions(+), 137 deletions(-) diff --git a/src/cmd.rs b/src/cmd.rs index 9b6bbbc..fbb844f 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -484,10 +484,7 @@ impl Cmd { Cmd::LIndex(key, index) => lindex_cmd(server, &key, index).await, Cmd::LRange(key, start, stop) => lrange_cmd(server, &key, start, stop).await, Cmd::FlushDb => flushdb_cmd(server).await, - Cmd::Unknow(s) => { - println!("\x1b[31;1munknown command: {}\x1b[0m", s); - Ok(Protocol::err(&format!("ERR unknown command '{}'", s))) - } + Cmd::Unknow(s) => Ok(Protocol::err(&format!("ERR unknown command `{}`", s))), } } @@ -645,24 +642,21 @@ async fn incr_cmd(server: &Server, key: &String) -> Result { } fn config_get_cmd(name: &String, server: &Server) -> Result { - let mut result = Vec::new(); - result.push(Protocol::BulkString(name.clone())); + let value = match name.as_str() { + "dir" => Some(server.option.dir.clone()), + "dbfilename" => Some(format!("{}.db", server.selected_db)), + "databases" => Some("16".to_string()), // Hardcoded as per original logic + _ => None, + }; - match name.as_str() { - "dir" => { - result.push(Protocol::BulkString(server.option.dir.clone())); - Ok(Protocol::Array(result)) - } - "dbfilename" => { - result.push(Protocol::BulkString(format!("{}.db", server.selected_db))); - Ok(Protocol::Array(result)) - }, - "databases" => { - // This is hardcoded, as the feature was removed - result.push(Protocol::BulkString("16".to_string())); - Ok(Protocol::Array(result)) - }, - _ => Ok(Protocol::Array(vec![])), + if let Some(val) = value { + Ok(Protocol::Array(vec![ + Protocol::BulkString(name.clone()), + Protocol::BulkString(val), + ])) + } else { + // Return an empty array for unknown config options, which is standard Redis behavior + Ok(Protocol::Array(vec![])) } } diff --git a/src/server.rs b/src/server.rs index 8d294b0..80f64a1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -42,6 +42,13 @@ impl Server { let db_file_path = std::path::PathBuf::from(self.option.dir.clone()) .join(format!("{}.db", self.selected_db)); + // Ensure the directory exists before creating the database file + if let Some(parent_dir) = db_file_path.parent() { + std::fs::create_dir_all(parent_dir).map_err(|e| { + DBError(format!("Failed to create directory {}: {}", parent_dir.display(), e)) + })?; + } + println!("Creating new db file: {}", db_file_path.display()); let storage = Arc::new(Storage::new( diff --git a/src/storage.rs b/src/storage.rs index 36f0c43..083c9f5 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -225,12 +225,15 @@ impl Storage { 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 (key, field) = binding.0.value(); - (key.to_string(), field.to_string()) - }).collect(); - for (key,field) in keys { + 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 = lists_table.iter()?.map(|item| item.unwrap().0.value().to_string()).collect(); @@ -262,9 +265,20 @@ impl Storage { let read_txn = self.db.begin_read()?; let table = read_txn.open_table(TYPES_TABLE)?; - match table.get(key)? { - Some(type_val) => Ok(Some(type_val.value().to_string())), - None => Ok(None), + // 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) } } @@ -353,7 +367,7 @@ impl Storage { { 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 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 @@ -501,37 +515,32 @@ impl Storage { } pub fn hdel(&self, key: &str, fields: &[String]) -> Result { - let write_txn = self.db.begin_write()?; - let mut deleted = 0u64; - - { - let types_table = write_txn.open_table(TYPES_TABLE)?; - let key_type = types_table.get(key)?; - match key_type { - Some(type_val) if type_val.value() == "hash" => { + // Enforce type check before proceeding to write transaction + let key_type = self.get_key_type(key)?; + match key_type.as_deref() { + Some("hash") => { + let write_txn = self.db.begin_write()?; + let mut deleted = 0u64; + { 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; } } } - Some(_) => return Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), - None => {} + write_txn.commit()?; + Ok(deleted) } + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(0), // Key doesn't exist, so 0 fields deleted. } - - write_txn.commit()?; - Ok(deleted) } pub fn hexists(&self, key: &str, field: &str) -> Result { - 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() == "hash" => { + match self.get_key_type(key)?.as_deref() { + Some("hash") => { + let read_txn = self.db.begin_read()?; let hashes_table = read_txn.open_table(HASHES_TABLE)?; Ok(hashes_table.get((key, field))?.is_some()) } @@ -541,23 +550,14 @@ impl Storage { } pub fn hkeys(&self, key: &str) -> Result, 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() == "hash" => { + match self.get_key_type(key)?.as_deref() { + Some("hash") => { + let read_txn = self.db.begin_read()?; 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()); - } + for entry in hashes_table.range((key, "")..=(key, "\u{FFFF}"))? { + result.push(entry?.0.value().1.to_string()); } - Ok(result) } Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), @@ -566,26 +566,15 @@ impl Storage { } pub fn hvals(&self, key: &str) -> Result, 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() == "hash" => { + match self.get_key_type(key)?.as_deref() { + Some("hash") => { + let read_txn = self.db.begin_read()?; 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(); - let value = entry.1.value(); - if hash_key == key { - let decrypted = self.decrypt_if_needed(value)?; - let value_str = String::from_utf8(decrypted)?; - result.push(value_str); - } + for entry in hashes_table.range((key, "")..=(key, "\u{FFFF}"))? { + let value = self.decrypt_if_needed(entry?.1.value())?; + result.push(String::from_utf8(value)?); } - Ok(result) } Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), @@ -594,24 +583,12 @@ impl Storage { } pub fn hlen(&self, key: &str) -> Result { - 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() == "hash" => { + match self.get_key_type(key)?.as_deref() { + Some("hash") => { + let read_txn = self.db.begin_read()?; let hashes_table = read_txn.open_table(HASHES_TABLE)?; - let mut count = 0u64; - - 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) + // Use `range` for efficiency + Ok(hashes_table.range((key, "")..=(key, "\u{FFFF}"))?.count() as u64) } Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), None => Ok(0), @@ -619,29 +596,22 @@ impl Storage { } pub fn hmget(&self, key: &str, fields: &[String]) -> Result>, 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() == "hash" => { + match self.get_key_type(key)?.as_deref() { + Some("hash") => { + let read_txn = self.db.begin_read()?; 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), - } + let value = match hashes_table.get((key, field.as_str()))? { + Some(data) => Some(String::from_utf8(self.decrypt_if_needed(data.value())?)?), + None => None, + }; + result.push(value); } - Ok(result) } Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), - None => Ok(fields.iter().map(|_| None).collect()), + None => Ok(vec![None; fields.len()]), } } @@ -684,16 +654,19 @@ impl Storage { pub fn scan(&self, cursor: u64, pattern: Option<&str>, count: Option) -> Result<(u64, Vec), DBError> { let read_txn = self.db.begin_read()?; - let table = read_txn.open_table(TYPES_TABLE)?; + + // Explicitly specify the table type to avoid confusion + let types_table: redb::ReadOnlyTable<&str, &str> = read_txn.open_table(TYPES_TABLE)?; let count = count.unwrap_or(10); // Default count is 10 let mut keys = Vec::new(); let mut current_cursor = 0u64; let mut returned_keys = 0u64; - let mut iter = table.iter()?; + let mut iter = types_table.iter()?; while let Some(entry) = iter.next() { - let key = entry?.0.value().to_string(); + let entry = entry?; + let key = entry.0.value().to_string(); // Skip keys until we reach the cursor position if current_cursor < cursor { @@ -707,15 +680,8 @@ impl Storage { if pat == "*" { true } else if pat.contains('*') { - // Simple glob pattern matching - let pattern_parts: Vec<&str> = pat.split('*').collect(); - if pattern_parts.len() == 2 { - let prefix = pattern_parts[0]; - let suffix = pattern_parts[1]; - key.starts_with(prefix) && key.ends_with(suffix) - } else { - key.contains(&pat.replace('*', "")) - } + // Use the glob_match function for better pattern matching + glob_match(pat, &key) } else { key.contains(pat) } @@ -746,10 +712,10 @@ impl Storage { let read_txn = self.db.begin_read()?; // Check if key exists and is a hash - let types_table = read_txn.open_table(TYPES_TABLE)?; + let types_table: redb::ReadOnlyTable<&str, &str> = read_txn.open_table(TYPES_TABLE)?; match types_table.get(key)? { Some(type_val) if type_val.value() == "hash" => { - let hashes_table = read_txn.open_table(HASHES_TABLE)?; + let hashes_table: redb::ReadOnlyTable<(&str, &str), &[u8]> = read_txn.open_table(HASHES_TABLE)?; let count = count.unwrap_or(10); let mut fields = Vec::new(); let mut current_cursor = 0u64; @@ -777,14 +743,8 @@ impl Storage { if pat == "*" { true } else if pat.contains('*') { - let pattern_parts: Vec<&str> = pat.split('*').collect(); - if pattern_parts.len() == 2 { - let prefix = pattern_parts[0]; - let suffix = pattern_parts[1]; - field.starts_with(prefix) && field.ends_with(suffix) - } else { - field.contains(&pat.replace('*', "")) - } + // Use the glob_match function for better pattern matching + glob_match(pat, field) } else { field.contains(pat) } @@ -807,7 +767,8 @@ impl Storage { current_cursor += 1; } - let next_cursor = if iter.next().is_none() { 0 } else { current_cursor }; + // Check if there are more entries by trying to get the next one + let next_cursor = if returned_fields < count { 0 } else { current_cursor }; Ok((next_cursor, fields)) } Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),