From 9177fa4091c24f77acbb518911b9eb4aa421bf8c Mon Sep 17 00:00:00 2001 From: despiegk Date: Mon, 18 Aug 2025 12:30:20 +0200 Subject: [PATCH] ... --- herodb/build.sh | 9 + herodb/run_tests.sh | 1 - herodb/src/storage/storage_hset.rs | 225 ++++++++++++++++-------- herodb/src/storage/storage_lists.rs | 2 +- herodb/tests/simple_integration_test.rs | 20 ++- 5 files changed, 176 insertions(+), 81 deletions(-) create mode 100755 herodb/build.sh diff --git a/herodb/build.sh b/herodb/build.sh new file mode 100755 index 0000000..b5c38d8 --- /dev/null +++ b/herodb/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -euo pipefail +export SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +echo "I am in $SCRIPT_DIR" +cd "$SCRIPT_DIR" + +cargo build + diff --git a/herodb/run_tests.sh b/herodb/run_tests.sh index 8f771d7..223fade 100755 --- a/herodb/run_tests.sh +++ b/herodb/run_tests.sh @@ -12,7 +12,6 @@ echo "" echo "2️⃣ Running Comprehensive Redis Integration Tests (13 tests)..." echo "----------------------------------------------------------------" cargo test -p herodb --test redis_integration_tests -- --nocapture -cargo test -p herodb --test redis_basic_client -- --nocapture cargo test -p herodb --test debug_hset -- --nocapture cargo test -p herodb --test debug_hset_simple -- --nocapture diff --git a/herodb/src/storage/storage_hset.rs b/herodb/src/storage/storage_hset.rs index b9ae3c4..e2d3130 100644 --- a/herodb/src/storage/storage_hset.rs +++ b/herodb/src/storage/storage_hset.rs @@ -12,20 +12,30 @@ impl Storage { let mut types_table = write_txn.open_table(TYPES_TABLE)?; let mut hashes_table = write_txn.open_table(HASHES_TABLE)?; - // Set the type to 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; + 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())), } } @@ -38,8 +48,10 @@ impl Storage { 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" => { + 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) => { @@ -50,7 +62,8 @@ impl Storage { None => Ok(None), } } - _ => Ok(None), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(None), } } @@ -58,9 +71,13 @@ impl Storage { pub fn hgetall(&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" => { + 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(); @@ -77,7 +94,8 @@ impl Storage { Ok(result) } - _ => Ok(Vec::new()), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(Vec::new()), } } @@ -86,41 +104,42 @@ impl Storage { let mut deleted = 0i64; // First check if key exists and is a hash - let is_hash = { + let key_type = { let types_table = write_txn.open_table(TYPES_TABLE)?; - let result = match types_table.get(key)? { - Some(type_val) => type_val.value() == "hash", - None => false, - }; - result + let access_guard = types_table.get(key)?; + access_guard.map(|v| v.value().to_string()) }; - if is_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; + 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)?; } } - - // 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()?; @@ -131,12 +150,19 @@ impl Storage { 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" => { + 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()) } - _ => Ok(false), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(false), } } @@ -144,8 +170,14 @@ impl Storage { 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" => { + 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(); @@ -160,7 +192,8 @@ impl Storage { Ok(result) } - _ => Ok(Vec::new()), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(Vec::new()), } } @@ -169,8 +202,14 @@ impl Storage { 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" => { + 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(); @@ -187,7 +226,8 @@ impl Storage { Ok(result) } - _ => Ok(Vec::new()), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(Vec::new()), } } @@ -195,8 +235,14 @@ impl Storage { 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" => { + 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; @@ -211,7 +257,8 @@ impl Storage { Ok(count) } - _ => Ok(0), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(0), } } @@ -220,8 +267,14 @@ impl Storage { 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" => { + 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(); @@ -238,7 +291,8 @@ impl Storage { Ok(result) } - _ => Ok(fields.into_iter().map(|_| None).collect()), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok(fields.into_iter().map(|_| None).collect()), } } @@ -251,15 +305,25 @@ impl Storage { let mut types_table = write_txn.open_table(TYPES_TABLE)?; let mut hashes_table = write_txn.open_table(HASHES_TABLE)?; - // Check if field already exists - if hashes_table.get((key, field))?.is_none() { - // Set the type to 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; + 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())), } } @@ -272,8 +336,14 @@ impl Storage { 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" => { + 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; @@ -312,7 +382,8 @@ impl Storage { let next_cursor = if result.len() < limit { 0 } else { current_cursor }; Ok((next_cursor, result)) } - _ => Ok((0, Vec::new())), + Some(_) => Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())), + None => Ok((0, Vec::new())), } } } \ No newline at end of file diff --git a/herodb/src/storage/storage_lists.rs b/herodb/src/storage/storage_lists.rs index 6dfd381..93a2ef6 100644 --- a/herodb/src/storage/storage_lists.rs +++ b/herodb/src/storage/storage_lists.rs @@ -25,7 +25,7 @@ impl Storage { }; // Add elements to the front (left) - for element in elements.into_iter().rev() { + for element in elements.into_iter() { list.insert(0, element); } diff --git a/herodb/tests/simple_integration_test.rs b/herodb/tests/simple_integration_test.rs index 729d429..52e2eb9 100644 --- a/herodb/tests/simple_integration_test.rs +++ b/herodb/tests/simple_integration_test.rs @@ -93,9 +93,16 @@ async fn test_basic_redis_functionality() { assert!(response.contains("string")); // Test QUIT to close connection gracefully - let response = send_redis_command(port, "*1\r\n$4\r\nQUIT\r\n").await; + let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).await.unwrap(); + stream.write_all("*1\r\n$4\r\nQUIT\r\n".as_bytes()).await.unwrap(); + let mut buffer = [0; 1024]; + let n = stream.read(&mut buffer).await.unwrap(); + let response = String::from_utf8_lossy(&buffer[..n]); assert!(response.contains("OK")); + // Ensure the stream is closed + stream.shutdown().await.unwrap(); + // Stop the server server_handle.abort(); @@ -149,6 +156,8 @@ async fn test_hash_operations() { assert!(response.contains("value2")); // Stop the server + // For hash operations, we don't have a persistent stream, so we'll just abort the server. + // The server should handle closing its connections. server_handle.abort(); println!("✅ All hash operations tests passed!"); @@ -202,9 +211,16 @@ async fn test_transaction_operations() { assert!(response.contains("OK")); // Should contain array of OK responses // Verify commands were executed - let response = send_redis_command(port, "*2\r\n$3\r\nGET\r\n$4\r\nkey1\r\n").await; + stream.write_all("*2\r\n$3\r\nGET\r\n$4\r\nkey1\r\n".as_bytes()).await.unwrap(); + let n = stream.read(&mut buffer).await.unwrap(); + let response = String::from_utf8_lossy(&buffer[..n]); assert!(response.contains("value1")); + stream.write_all("*2\r\n$3\r\nGET\r\n$4\r\nkey2\r\n".as_bytes()).await.unwrap(); + let n = stream.read(&mut buffer).await.unwrap(); + let response = String::from_utf8_lossy(&buffer[..n]); + assert!(response.contains("value2")); + // Stop the server server_handle.abort();