...
This commit is contained in:
9
herodb/build.sh
Executable file
9
herodb/build.sh
Executable file
@@ -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
|
||||||
|
|
@@ -12,7 +12,6 @@ echo ""
|
|||||||
echo "2️⃣ Running Comprehensive Redis Integration Tests (13 tests)..."
|
echo "2️⃣ Running Comprehensive Redis Integration Tests (13 tests)..."
|
||||||
echo "----------------------------------------------------------------"
|
echo "----------------------------------------------------------------"
|
||||||
cargo test -p herodb --test redis_integration_tests -- --nocapture
|
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 -- --nocapture
|
||||||
cargo test -p herodb --test debug_hset_simple -- --nocapture
|
cargo test -p herodb --test debug_hset_simple -- --nocapture
|
||||||
|
|
||||||
|
@@ -12,7 +12,14 @@ impl Storage {
|
|||||||
let mut types_table = write_txn.open_table(TYPES_TABLE)?;
|
let mut types_table = write_txn.open_table(TYPES_TABLE)?;
|
||||||
let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
|
let mut hashes_table = write_txn.open_table(HASHES_TABLE)?;
|
||||||
|
|
||||||
// Set the type to 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") | None => { // Proceed if hash or new key
|
||||||
|
// Set the type to hash (only if new key or existing hash)
|
||||||
types_table.insert(key, "hash")?;
|
types_table.insert(key, "hash")?;
|
||||||
|
|
||||||
for (field, value) in pairs {
|
for (field, value) in pairs {
|
||||||
@@ -28,6 +35,9 @@ impl Storage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(_) => return Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
write_txn.commit()?;
|
write_txn.commit()?;
|
||||||
Ok(new_fields)
|
Ok(new_fields)
|
||||||
@@ -38,8 +48,10 @@ impl Storage {
|
|||||||
let read_txn = self.db.begin_read()?;
|
let read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match types_table.get(key)? {
|
let key_type = types_table.get(key)?.map(|v| v.value().to_string());
|
||||||
Some(type_val) if type_val.value() == "hash" => {
|
|
||||||
|
match key_type.as_deref() {
|
||||||
|
Some("hash") => {
|
||||||
let hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
let hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
||||||
match hashes_table.get((key, field))? {
|
match hashes_table.get((key, field))? {
|
||||||
Some(data) => {
|
Some(data) => {
|
||||||
@@ -50,7 +62,8 @@ impl Storage {
|
|||||||
None => Ok(None),
|
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<Vec<(String, String)>, DBError> {
|
pub fn hgetall(&self, key: &str) -> Result<Vec<(String, String)>, DBError> {
|
||||||
let read_txn = self.db.begin_read()?;
|
let read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
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 types_table.get(key)? {
|
match key_type.as_deref() {
|
||||||
Some(type_val) if type_val.value() == "hash" => {
|
Some("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();
|
||||||
|
|
||||||
@@ -77,7 +94,8 @@ impl Storage {
|
|||||||
|
|
||||||
Ok(result)
|
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,16 +104,14 @@ impl Storage {
|
|||||||
let mut deleted = 0i64;
|
let mut deleted = 0i64;
|
||||||
|
|
||||||
// First check if key exists and is a hash
|
// 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 types_table = write_txn.open_table(TYPES_TABLE)?;
|
||||||
let result = match types_table.get(key)? {
|
let access_guard = types_table.get(key)?;
|
||||||
Some(type_val) => type_val.value() == "hash",
|
access_guard.map(|v| v.value().to_string())
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
result
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_hash {
|
match key_type.as_deref() {
|
||||||
|
Some("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 {
|
||||||
@@ -122,6 +138,9 @@ impl Storage {
|
|||||||
types_table.remove(key)?;
|
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()?;
|
write_txn.commit()?;
|
||||||
Ok(deleted)
|
Ok(deleted)
|
||||||
@@ -131,12 +150,19 @@ impl Storage {
|
|||||||
let read_txn = self.db.begin_read()?;
|
let read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match types_table.get(key)? {
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
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 hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
||||||
Ok(hashes_table.get((key, field))?.is_some())
|
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 read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match types_table.get(key)? {
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
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 hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
@@ -160,7 +192,8 @@ impl Storage {
|
|||||||
|
|
||||||
Ok(result)
|
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 read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match types_table.get(key)? {
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
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 hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
@@ -187,7 +226,8 @@ impl Storage {
|
|||||||
|
|
||||||
Ok(result)
|
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 read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match types_table.get(key)? {
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
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 hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut count = 0i64;
|
let mut count = 0i64;
|
||||||
|
|
||||||
@@ -211,7 +257,8 @@ impl Storage {
|
|||||||
|
|
||||||
Ok(count)
|
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 read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match types_table.get(key)? {
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
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 hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
@@ -238,7 +291,8 @@ impl Storage {
|
|||||||
|
|
||||||
Ok(result)
|
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,9 +305,16 @@ impl Storage {
|
|||||||
let mut types_table = write_txn.open_table(TYPES_TABLE)?;
|
let mut types_table = write_txn.open_table(TYPES_TABLE)?;
|
||||||
let mut hashes_table = write_txn.open_table(HASHES_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
|
// Check if field already exists
|
||||||
if hashes_table.get((key, field))?.is_none() {
|
if hashes_table.get((key, field))?.is_none() {
|
||||||
// Set the type to hash
|
// Set the type to hash (only if new key or existing hash)
|
||||||
types_table.insert(key, "hash")?;
|
types_table.insert(key, "hash")?;
|
||||||
|
|
||||||
// Encrypt the value before storing
|
// Encrypt the value before storing
|
||||||
@@ -262,6 +323,9 @@ impl Storage {
|
|||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(_) => return Err(DBError("WRONGTYPE Operation against a key holding the wrong kind of value".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
write_txn.commit()?;
|
write_txn.commit()?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@@ -272,8 +336,14 @@ impl Storage {
|
|||||||
let read_txn = self.db.begin_read()?;
|
let read_txn = self.db.begin_read()?;
|
||||||
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
|
|
||||||
match types_table.get(key)? {
|
let types_table = read_txn.open_table(TYPES_TABLE)?;
|
||||||
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 hashes_table = read_txn.open_table(HASHES_TABLE)?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
let mut current_cursor = 0u64;
|
let mut current_cursor = 0u64;
|
||||||
@@ -312,7 +382,8 @@ impl Storage {
|
|||||||
let next_cursor = if result.len() < limit { 0 } else { current_cursor };
|
let next_cursor = if result.len() < limit { 0 } else { current_cursor };
|
||||||
Ok((next_cursor, result))
|
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())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -25,7 +25,7 @@ impl Storage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add elements to the front (left)
|
// Add elements to the front (left)
|
||||||
for element in elements.into_iter().rev() {
|
for element in elements.into_iter() {
|
||||||
list.insert(0, element);
|
list.insert(0, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -93,9 +93,16 @@ async fn test_basic_redis_functionality() {
|
|||||||
assert!(response.contains("string"));
|
assert!(response.contains("string"));
|
||||||
|
|
||||||
// Test QUIT to close connection gracefully
|
// 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"));
|
assert!(response.contains("OK"));
|
||||||
|
|
||||||
|
// Ensure the stream is closed
|
||||||
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
// Stop the server
|
// Stop the server
|
||||||
server_handle.abort();
|
server_handle.abort();
|
||||||
|
|
||||||
@@ -149,6 +156,8 @@ async fn test_hash_operations() {
|
|||||||
assert!(response.contains("value2"));
|
assert!(response.contains("value2"));
|
||||||
|
|
||||||
// Stop the server
|
// 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();
|
server_handle.abort();
|
||||||
|
|
||||||
println!("✅ All hash operations tests passed!");
|
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
|
assert!(response.contains("OK")); // Should contain array of OK responses
|
||||||
|
|
||||||
// Verify commands were executed
|
// 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"));
|
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
|
// Stop the server
|
||||||
server_handle.abort();
|
server_handle.abort();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user