it works
This commit is contained in:
36
src/cmd.rs
36
src/cmd.rs
@@ -484,10 +484,7 @@ impl Cmd {
|
|||||||
Cmd::LIndex(key, index) => lindex_cmd(server, &key, index).await,
|
Cmd::LIndex(key, index) => lindex_cmd(server, &key, index).await,
|
||||||
Cmd::LRange(key, start, stop) => lrange_cmd(server, &key, start, stop).await,
|
Cmd::LRange(key, start, stop) => lrange_cmd(server, &key, start, stop).await,
|
||||||
Cmd::FlushDb => flushdb_cmd(server).await,
|
Cmd::FlushDb => flushdb_cmd(server).await,
|
||||||
Cmd::Unknow(s) => {
|
Cmd::Unknow(s) => Ok(Protocol::err(&format!("ERR unknown command `{}`", s))),
|
||||||
println!("\x1b[31;1munknown command: {}\x1b[0m", s);
|
|
||||||
Ok(Protocol::err(&format!("ERR unknown command '{}'", s)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,24 +642,21 @@ async fn incr_cmd(server: &Server, key: &String) -> Result<Protocol, DBError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn config_get_cmd(name: &String, server: &Server) -> Result<Protocol, DBError> {
|
fn config_get_cmd(name: &String, server: &Server) -> Result<Protocol, DBError> {
|
||||||
let mut result = Vec::new();
|
let value = match name.as_str() {
|
||||||
result.push(Protocol::BulkString(name.clone()));
|
"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() {
|
if let Some(val) = value {
|
||||||
"dir" => {
|
Ok(Protocol::Array(vec![
|
||||||
result.push(Protocol::BulkString(server.option.dir.clone()));
|
Protocol::BulkString(name.clone()),
|
||||||
Ok(Protocol::Array(result))
|
Protocol::BulkString(val),
|
||||||
}
|
]))
|
||||||
"dbfilename" => {
|
} else {
|
||||||
result.push(Protocol::BulkString(format!("{}.db", server.selected_db)));
|
// Return an empty array for unknown config options, which is standard Redis behavior
|
||||||
Ok(Protocol::Array(result))
|
Ok(Protocol::Array(vec![]))
|
||||||
},
|
|
||||||
"databases" => {
|
|
||||||
// This is hardcoded, as the feature was removed
|
|
||||||
result.push(Protocol::BulkString("16".to_string()));
|
|
||||||
Ok(Protocol::Array(result))
|
|
||||||
},
|
|
||||||
_ => Ok(Protocol::Array(vec![])),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,6 +42,13 @@ impl Server {
|
|||||||
let db_file_path = std::path::PathBuf::from(self.option.dir.clone())
|
let db_file_path = std::path::PathBuf::from(self.option.dir.clone())
|
||||||
.join(format!("{}.db", self.selected_db));
|
.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());
|
println!("Creating new db file: {}", db_file_path.display());
|
||||||
|
|
||||||
let storage = Arc::new(Storage::new(
|
let storage = Arc::new(Storage::new(
|
||||||
|
193
src/storage.rs
193
src/storage.rs
@@ -225,12 +225,15 @@ impl Storage {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
strings_table.remove(key.as_str())?;
|
strings_table.remove(key.as_str())?;
|
||||||
}
|
}
|
||||||
let keys: Vec<(String,String)> = hashes_table.iter()?.map(|item| {
|
let keys: Vec<(String, String)> = hashes_table
|
||||||
let binding = item.unwrap();
|
.iter()?
|
||||||
let (key, field) = binding.0.value();
|
.map(|item| {
|
||||||
(key.to_string(), field.to_string())
|
let binding = item.unwrap();
|
||||||
}).collect();
|
let (k, f) = binding.0.value();
|
||||||
for (key,field) in keys {
|
(k.to_string(), f.to_string())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for (key, field) in keys {
|
||||||
hashes_table.remove((key.as_str(), field.as_str()))?;
|
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();
|
let keys: Vec<String> = 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 read_txn = self.db.begin_read()?;
|
||||||
let table = read_txn.open_table(TYPES_TABLE)?;
|
let table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match table.get(key)? {
|
// Before returning type, check for expiration
|
||||||
Some(type_val) => Ok(Some(type_val.value().to_string())),
|
if let Some(type_val) = table.get(key)? {
|
||||||
None => Ok(None),
|
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 types_table = write_txn.open_table(TYPES_TABLE)?;
|
||||||
let mut strings_table = write_txn.open_table(STRINGS_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)?;
|
let mut lists_table = write_txn.open_table(LISTS_TABLE)?;
|
||||||
|
|
||||||
// Remove from type table
|
// Remove from type table
|
||||||
@@ -501,37 +515,32 @@ impl Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hdel(&self, key: &str, fields: &[String]) -> Result<u64, DBError> {
|
pub fn hdel(&self, key: &str, fields: &[String]) -> Result<u64, DBError> {
|
||||||
let write_txn = self.db.begin_write()?;
|
// Enforce type check before proceeding to write transaction
|
||||||
let mut deleted = 0u64;
|
let key_type = self.get_key_type(key)?;
|
||||||
|
match key_type.as_deref() {
|
||||||
{
|
Some("hash") => {
|
||||||
let types_table = write_txn.open_table(TYPES_TABLE)?;
|
let write_txn = self.db.begin_write()?;
|
||||||
let key_type = types_table.get(key)?;
|
let mut deleted = 0u64;
|
||||||
match key_type {
|
{
|
||||||
Some(type_val) if type_val.value() == "hash" => {
|
|
||||||
let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
|
let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
|
||||||
|
|
||||||
for field in fields {
|
for field in fields {
|
||||||
if hashes_table.remove((key, field.as_str()))?.is_some() {
|
if hashes_table.remove((key, field.as_str()))?.is_some() {
|
||||||
deleted += 1;
|
deleted += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(_) => return Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
write_txn.commit()?;
|
||||||
None => {}
|
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<bool, DBError> {
|
pub fn hexists(&self, key: &str, field: &str) -> Result<bool, DBError> {
|
||||||
let read_txn = self.db.begin_read()?;
|
match self.get_key_type(key)?.as_deref() {
|
||||||
|
Some("hash") => {
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let read_txn = self.db.begin_read()?;
|
||||||
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 = read_txn.open_table(HASHES_TABLE)?;
|
||||||
Ok(hashes_table.get((key, field))?.is_some())
|
Ok(hashes_table.get((key, field))?.is_some())
|
||||||
}
|
}
|
||||||
@@ -541,23 +550,14 @@ impl Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hkeys(&self, key: &str) -> Result<Vec<String>, DBError> {
|
pub fn hkeys(&self, key: &str) -> Result<Vec<String>, DBError> {
|
||||||
let read_txn = self.db.begin_read()?;
|
match self.get_key_type(key)?.as_deref() {
|
||||||
|
Some("hash") => {
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let read_txn = self.db.begin_read()?;
|
||||||
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 = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
for entry in hashes_table.range((key, "")..=(key, "\u{FFFF}"))? {
|
||||||
let mut iter = hashes_table.iter()?;
|
result.push(entry?.0.value().1.to_string());
|
||||||
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)
|
Ok(result)
|
||||||
}
|
}
|
||||||
Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
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<Vec<String>, DBError> {
|
pub fn hvals(&self, key: &str) -> Result<Vec<String>, DBError> {
|
||||||
let read_txn = self.db.begin_read()?;
|
match self.get_key_type(key)?.as_deref() {
|
||||||
|
Some("hash") => {
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let read_txn = self.db.begin_read()?;
|
||||||
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 = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
for entry in hashes_table.range((key, "")..=(key, "\u{FFFF}"))? {
|
||||||
let mut iter = hashes_table.iter()?;
|
let value = self.decrypt_if_needed(entry?.1.value())?;
|
||||||
while let Some(entry) = iter.next() {
|
result.push(String::from_utf8(value)?);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
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<u64, DBError> {
|
pub fn hlen(&self, key: &str) -> Result<u64, DBError> {
|
||||||
let read_txn = self.db.begin_read()?;
|
match self.get_key_type(key)?.as_deref() {
|
||||||
|
Some("hash") => {
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let read_txn = self.db.begin_read()?;
|
||||||
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 = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut count = 0u64;
|
// Use `range` for efficiency
|
||||||
|
Ok(hashes_table.range((key, "")..=(key, "\u{FFFF}"))?.count() as u64)
|
||||||
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())),
|
Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
||||||
None => Ok(0),
|
None => Ok(0),
|
||||||
@@ -619,29 +596,22 @@ impl Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hmget(&self, key: &str, fields: &[String]) -> Result<Vec<Option<String>>, DBError> {
|
pub fn hmget(&self, key: &str, fields: &[String]) -> Result<Vec<Option<String>>, DBError> {
|
||||||
let read_txn = self.db.begin_read()?;
|
match self.get_key_type(key)?.as_deref() {
|
||||||
|
Some("hash") => {
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let read_txn = self.db.begin_read()?;
|
||||||
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 = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
for field in fields {
|
for field in fields {
|
||||||
match hashes_table.get((key, field.as_str()))? {
|
let value = match hashes_table.get((key, field.as_str()))? {
|
||||||
Some(data) => {
|
Some(data) => Some(String::from_utf8(self.decrypt_if_needed(data.value())?)?),
|
||||||
let decrypted = self.decrypt_if_needed(data.value())?;
|
None => None,
|
||||||
let value = String::from_utf8(decrypted)?;
|
};
|
||||||
result.push(Some(value));
|
result.push(value);
|
||||||
}
|
|
||||||
None => result.push(None),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
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<u64>) -> Result<(u64, Vec<String>), DBError> {
|
pub fn scan(&self, cursor: u64, pattern: Option<&str>, count: Option<u64>) -> Result<(u64, Vec<String>), DBError> {
|
||||||
let read_txn = self.db.begin_read()?;
|
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 count = count.unwrap_or(10); // Default count is 10
|
||||||
let mut keys = Vec::new();
|
let mut keys = Vec::new();
|
||||||
let mut current_cursor = 0u64;
|
let mut current_cursor = 0u64;
|
||||||
let mut returned_keys = 0u64;
|
let mut returned_keys = 0u64;
|
||||||
|
|
||||||
let mut iter = table.iter()?;
|
let mut iter = types_table.iter()?;
|
||||||
while let Some(entry) = iter.next() {
|
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
|
// Skip keys until we reach the cursor position
|
||||||
if current_cursor < cursor {
|
if current_cursor < cursor {
|
||||||
@@ -707,15 +680,8 @@ impl Storage {
|
|||||||
if pat == "*" {
|
if pat == "*" {
|
||||||
true
|
true
|
||||||
} else if pat.contains('*') {
|
} else if pat.contains('*') {
|
||||||
// Simple glob pattern matching
|
// Use the glob_match function for better pattern matching
|
||||||
let pattern_parts: Vec<&str> = pat.split('*').collect();
|
glob_match(pat, &key)
|
||||||
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('*', ""))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
key.contains(pat)
|
key.contains(pat)
|
||||||
}
|
}
|
||||||
@@ -746,10 +712,10 @@ impl Storage {
|
|||||||
let read_txn = self.db.begin_read()?;
|
let read_txn = self.db.begin_read()?;
|
||||||
|
|
||||||
// Check if key exists and is a hash
|
// 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)? {
|
match types_table.get(key)? {
|
||||||
Some(type_val) if type_val.value() == "hash" => {
|
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 count = count.unwrap_or(10);
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
let mut current_cursor = 0u64;
|
let mut current_cursor = 0u64;
|
||||||
@@ -777,14 +743,8 @@ impl Storage {
|
|||||||
if pat == "*" {
|
if pat == "*" {
|
||||||
true
|
true
|
||||||
} else if pat.contains('*') {
|
} else if pat.contains('*') {
|
||||||
let pattern_parts: Vec<&str> = pat.split('*').collect();
|
// Use the glob_match function for better pattern matching
|
||||||
if pattern_parts.len() == 2 {
|
glob_match(pat, field)
|
||||||
let prefix = pattern_parts[0];
|
|
||||||
let suffix = pattern_parts[1];
|
|
||||||
field.starts_with(prefix) && field.ends_with(suffix)
|
|
||||||
} else {
|
|
||||||
field.contains(&pat.replace('*', ""))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
field.contains(pat)
|
field.contains(pat)
|
||||||
}
|
}
|
||||||
@@ -807,7 +767,8 @@ impl Storage {
|
|||||||
current_cursor += 1;
|
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))
|
Ok((next_cursor, fields))
|
||||||
}
|
}
|
||||||
Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
||||||
|
Reference in New Issue
Block a user