diff --git a/src/cmd.rs b/src/cmd.rs index 833b3c3..4c52ea2 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -4,6 +4,7 @@ use crate::{error::DBError, protocol::Protocol, server::Server}; pub enum Cmd { Ping, Echo(String), + Select(u16), Get(String), Set(String, String), SetPx(String, String, u128), @@ -60,6 +61,13 @@ impl Cmd { } Ok(( match cmd[0].to_lowercase().as_str() { + "select" => { + if cmd.len() != 2 { + return Err(DBError("wrong number of arguments for SELECT".to_string())); + } + let idx = cmd[1].parse::().map_err(|_| DBError("ERR DB index is not an integer".to_string()))?; + Cmd::Select(idx) + } "echo" => Cmd::Echo(cmd[1].clone()), "ping" => Cmd::Ping, "get" => Cmd::Get(cmd[1].clone()), @@ -419,6 +427,7 @@ impl Cmd { } match self { + Cmd::Select(db) => select_cmd(server, *db).await, Cmd::Ping => Ok(Protocol::SimpleString("PONG".to_string())), Cmd::Echo(s) => Ok(Protocol::SimpleString(s.clone())), Cmd::Get(k) => get_cmd(server, k).await, @@ -480,9 +489,17 @@ impl Cmd { } } } +async fn select_cmd(server: &mut Server, db: u16) -> Result { + let idx = db as usize; + if idx >= server.storages.len() { + return Ok(Protocol::err("ERR DB index is out of range")); + } + server.selected_db = idx; + Ok(Protocol::SimpleString("OK".to_string())) +} async fn lindex_cmd(server: &Server, key: &str, index: i64) -> Result { - match server.storage.lindex(key, index) { + match server.current_storage().lindex(key, index) { Ok(Some(element)) => Ok(Protocol::BulkString(element)), Ok(None) => Ok(Protocol::Null), Err(e) => Ok(Protocol::err(&e.0)), @@ -490,35 +507,35 @@ async fn lindex_cmd(server: &Server, key: &str, index: i64) -> Result Result { - match server.storage.lrange(key, start, stop) { + match server.current_storage().lrange(key, start, stop) { Ok(elements) => Ok(Protocol::Array(elements.into_iter().map(Protocol::BulkString).collect())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn ltrim_cmd(server: &Server, key: &str, start: i64, stop: i64) -> Result { - match server.storage.ltrim(key, start, stop) { + match server.current_storage().ltrim(key, start, stop) { Ok(_) => Ok(Protocol::SimpleString("OK".to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn lrem_cmd(server: &Server, key: &str, count: i64, element: &str) -> Result { - match server.storage.lrem(key, count, element) { + match server.current_storage().lrem(key, count, element) { Ok(removed_count) => Ok(Protocol::SimpleString(removed_count.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn llen_cmd(server: &Server, key: &str) -> Result { - match server.storage.llen(key) { + match server.current_storage().llen(key) { Ok(len) => Ok(Protocol::SimpleString(len.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn lpop_cmd(server: &Server, key: &str, count: &Option) -> Result { - match server.storage.lpop(key, *count) { + match server.current_storage().lpop(key, *count) { Ok(Some(elements)) => { if count.is_some() { Ok(Protocol::Array(elements.into_iter().map(Protocol::BulkString).collect())) @@ -538,7 +555,7 @@ async fn lpop_cmd(server: &Server, key: &str, count: &Option) -> Result) -> Result { - match server.storage.rpop(key, *count) { + match server.current_storage().rpop(key, *count) { Ok(Some(elements)) => { if count.is_some() { Ok(Protocol::Array(elements.into_iter().map(Protocol::BulkString).collect())) @@ -558,14 +575,14 @@ async fn rpop_cmd(server: &Server, key: &str, count: &Option) -> Result Result { - match server.storage.lpush(key, elements.to_vec()) { + match server.current_storage().lpush(key, elements.to_vec()) { Ok(len) => Ok(Protocol::SimpleString(len.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn rpush_cmd(server: &Server, key: &str, elements: &[String]) -> Result { - match server.storage.rpush(key, elements.to_vec()) { + match server.current_storage().rpush(key, elements.to_vec()) { Ok(len) => Ok(Protocol::SimpleString(len.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } @@ -589,7 +606,7 @@ async fn exec_cmd( } async fn incr_cmd(server: &Server, key: &String) -> Result { - let current_value = server.storage.get(key)?; + let current_value = server.current_storage().get(key)?; let new_value = match current_value { Some(v) => { @@ -601,7 +618,7 @@ async fn incr_cmd(server: &Server, key: &String) -> Result { None => 1, }; - server.storage.set(key.clone(), new_value.to_string())?; + server.current_storage().set(key.clone(), new_value.to_string())?; Ok(Protocol::SimpleString(new_value.to_string())) } @@ -613,18 +630,18 @@ fn config_get_cmd(name: &String, server: &Server) -> Result { ])), "dbfilename" => Ok(Protocol::Array(vec![ Protocol::BulkString(name.clone()), - Protocol::BulkString("herodb.redb".to_string()), + Protocol::BulkString(format!("{}.db", server.selected_db)), ])), "databases" => Ok(Protocol::Array(vec![ Protocol::BulkString(name.clone()), - Protocol::BulkString("16".to_string()), + Protocol::BulkString(server.option.databases.to_string()), ])), - _ => Ok(Protocol::Array(vec![])), // Return empty array for unknown configs instead of error + _ => Ok(Protocol::Array(vec![])), } } async fn keys_cmd(server: &Server) -> Result { - let keys = server.storage.keys("*")?; + let keys = server.current_storage().keys("*")?; Ok(Protocol::Array( keys.into_iter().map(Protocol::BulkString).collect(), )) @@ -643,14 +660,14 @@ fn info_cmd(section: &Option) -> Result { } async fn type_cmd(server: &Server, k: &String) -> Result { - match server.storage.get_key_type(k)? { + match server.current_storage().get_key_type(k)? { Some(type_str) => Ok(Protocol::SimpleString(type_str)), None => Ok(Protocol::SimpleString("none".to_string())), } } async fn del_cmd(server: &Server, k: &str) -> Result { - server.storage.del(k.to_string())?; + server.current_storage().del(k.to_string())?; Ok(Protocol::SimpleString("1".to_string())) } @@ -660,7 +677,7 @@ async fn set_ex_cmd( v: &str, x: &u128, ) -> Result { - server.storage.setx(k.to_string(), v.to_string(), *x * 1000)?; + server.current_storage().setx(k.to_string(), v.to_string(), *x * 1000)?; Ok(Protocol::SimpleString("OK".to_string())) } @@ -670,28 +687,28 @@ async fn set_px_cmd( v: &str, x: &u128, ) -> Result { - server.storage.setx(k.to_string(), v.to_string(), *x)?; + server.current_storage().setx(k.to_string(), v.to_string(), *x)?; Ok(Protocol::SimpleString("OK".to_string())) } async fn set_cmd(server: &Server, k: &str, v: &str) -> Result { - server.storage.set(k.to_string(), v.to_string())?; + server.current_storage().set(k.to_string(), v.to_string())?; Ok(Protocol::SimpleString("OK".to_string())) } async fn get_cmd(server: &Server, k: &str) -> Result { - let v = server.storage.get(k)?; + let v = server.current_storage().get(k)?; Ok(v.map_or(Protocol::Null, Protocol::BulkString)) } // Hash command implementations async fn hset_cmd(server: &Server, key: &str, pairs: &[(String, String)]) -> Result { - let new_fields = server.storage.hset(key, pairs)?; + let new_fields = server.current_storage().hset(key, pairs)?; Ok(Protocol::SimpleString(new_fields.to_string())) } async fn hget_cmd(server: &Server, key: &str, field: &str) -> Result { - match server.storage.hget(key, field) { + match server.current_storage().hget(key, field) { Ok(Some(value)) => Ok(Protocol::BulkString(value)), Ok(None) => Ok(Protocol::Null), Err(e) => Ok(Protocol::err(&e.0)), @@ -699,7 +716,7 @@ async fn hget_cmd(server: &Server, key: &str, field: &str) -> Result Result { - match server.storage.hgetall(key) { + match server.current_storage().hgetall(key) { Ok(pairs) => { let mut result = Vec::new(); for (field, value) in pairs { @@ -713,21 +730,21 @@ async fn hgetall_cmd(server: &Server, key: &str) -> Result { } async fn hdel_cmd(server: &Server, key: &str, fields: &[String]) -> Result { - match server.storage.hdel(key, fields) { + match server.current_storage().hdel(key, fields) { Ok(deleted) => Ok(Protocol::SimpleString(deleted.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn hexists_cmd(server: &Server, key: &str, field: &str) -> Result { - match server.storage.hexists(key, field) { + match server.current_storage().hexists(key, field) { Ok(exists) => Ok(Protocol::SimpleString(if exists { "1" } else { "0" }.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn hkeys_cmd(server: &Server, key: &str) -> Result { - match server.storage.hkeys(key) { + match server.current_storage().hkeys(key) { Ok(keys) => Ok(Protocol::Array( keys.into_iter().map(Protocol::BulkString).collect(), )), @@ -736,7 +753,7 @@ async fn hkeys_cmd(server: &Server, key: &str) -> Result { } async fn hvals_cmd(server: &Server, key: &str) -> Result { - match server.storage.hvals(key) { + match server.current_storage().hvals(key) { Ok(values) => Ok(Protocol::Array( values.into_iter().map(Protocol::BulkString).collect(), )), @@ -745,14 +762,14 @@ async fn hvals_cmd(server: &Server, key: &str) -> Result { } async fn hlen_cmd(server: &Server, key: &str) -> Result { - match server.storage.hlen(key) { + match server.current_storage().hlen(key) { Ok(len) => Ok(Protocol::SimpleString(len.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn hmget_cmd(server: &Server, key: &str, fields: &[String]) -> Result { - match server.storage.hmget(key, fields) { + match server.current_storage().hmget(key, fields) { Ok(values) => { let result: Vec = values .into_iter() @@ -765,14 +782,14 @@ async fn hmget_cmd(server: &Server, key: &str, fields: &[String]) -> Result Result { - match server.storage.hsetnx(key, field, value) { + match server.current_storage().hsetnx(key, field, value) { Ok(was_set) => Ok(Protocol::SimpleString(if was_set { "1" } else { "0" }.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn scan_cmd(server: &Server, cursor: &u64, pattern: Option<&str>, count: &Option) -> Result { - match server.storage.scan(*cursor, pattern, *count) { + match server.current_storage().scan(*cursor, pattern, *count) { Ok((next_cursor, keys)) => { let mut result = Vec::new(); result.push(Protocol::BulkString(next_cursor.to_string())); @@ -786,7 +803,7 @@ async fn scan_cmd(server: &Server, cursor: &u64, pattern: Option<&str>, count: & } async fn hscan_cmd(server: &Server, key: &str, cursor: &u64, pattern: Option<&str>, count: &Option) -> Result { - match server.storage.hscan(key, *cursor, pattern, *count) { + match server.current_storage().hscan(key, *cursor, pattern, *count) { Ok((next_cursor, fields)) => { let mut result = Vec::new(); result.push(Protocol::BulkString(next_cursor.to_string())); @@ -800,14 +817,14 @@ async fn hscan_cmd(server: &Server, key: &str, cursor: &u64, pattern: Option<&st } async fn ttl_cmd(server: &Server, key: &str) -> Result { - match server.storage.ttl(key) { + match server.current_storage().ttl(key) { Ok(ttl) => Ok(Protocol::SimpleString(ttl.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } } async fn exists_cmd(server: &Server, key: &str) -> Result { - match server.storage.exists(key) { + match server.current_storage().exists(key) { Ok(exists) => Ok(Protocol::SimpleString(if exists { "1" } else { "0" }.to_string())), Err(e) => Ok(Protocol::err(&e.0)), } diff --git a/src/main.rs b/src/main.rs index 40abf4b..ed128c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,10 @@ struct Args { /// Enable debug mode #[arg(long)] debug: bool, + + /// Number of logical databases (SELECT 0..N-1) + #[arg(long, default_value_t = 16)] + databases: u16, } #[tokio::main] @@ -41,6 +45,7 @@ async fn main() { dir: args.dir, port, debug: args.debug, + databases: args.databases, }; // new server diff --git a/src/options.rs b/src/options.rs index 6e50a1c..16095df 100644 --- a/src/options.rs +++ b/src/options.rs @@ -3,4 +3,5 @@ pub struct DBOption { pub dir: String, pub port: u16, pub debug: bool, + pub databases: u16, // number of logical DBs (default 16) } diff --git a/src/server.rs b/src/server.rs index c0c8e1e..e46de3c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -12,27 +12,36 @@ use crate::storage::Storage; #[derive(Clone)] pub struct Server { - pub storage: Arc, + pub storages: Vec>, pub option: options::DBOption, pub client_name: Option, + pub selected_db: usize, // per-connection } impl Server { pub async fn new(option: options::DBOption) -> Self { - // Create database file path with fixed filename - let db_file_path = PathBuf::from(option.dir.clone()).join("herodb.redb"); - println!("will open db file path: {}", db_file_path.display()); - - // Initialize storage with redb - let storage = Storage::new(db_file_path).expect("Failed to initialize storage"); + // Eagerly create N db files: /.db + let mut storages = Vec::with_capacity(option.databases as usize); + for i in 0..option.databases { + let db_file_path = PathBuf::from(option.dir.clone()).join(format!("{}.db", i)); + println!("will open db file path (db {}): {}", i, db_file_path.display()); + let storage = Storage::new(db_file_path).expect("Failed to initialize storage"); + storages.push(Arc::new(storage)); + } Server { - storage: Arc::new(storage), + storages, option, client_name: None, + selected_db: 0, } } + #[inline] + pub fn current_storage(&self) -> &Storage { + self.storages[self.selected_db].as_ref() + } + pub async fn handle( &mut self, mut stream: tokio::net::TcpStream, diff --git a/tests/debug_hset.rs b/tests/debug_hset.rs index 777b5f6..1aa0b02 100644 --- a/tests/debug_hset.rs +++ b/tests/debug_hset.rs @@ -25,6 +25,7 @@ async fn debug_hset_simple() { dir: test_dir.to_string(), port, debug: false, + databases: 16, }; let mut server = Server::new(option).await; diff --git a/tests/debug_hset_simple.rs b/tests/debug_hset_simple.rs index 1a2cb5c..5191649 100644 --- a/tests/debug_hset_simple.rs +++ b/tests/debug_hset_simple.rs @@ -16,6 +16,7 @@ async fn debug_hset_return_value() { dir: test_dir.to_string(), port: 16390, debug: false, + databases: 16, }; let mut server = Server::new(option).await; diff --git a/tests/redis_integration_tests.rs b/tests/redis_integration_tests.rs index 9366f8a..7a7758a 100644 --- a/tests/redis_integration_tests.rs +++ b/tests/redis_integration_tests.rs @@ -1,4 +1,4 @@ -use redis::{Client, Commands, Connection}; +use redis::{Client, Commands, Connection, RedisResult}; use std::process::{Child, Command}; use std::time::Duration; use tokio::time::sleep; @@ -77,6 +77,7 @@ fn setup_server() -> (ServerProcessGuard, u16) { &test_dir, "--port", &port.to_string(), + "--debug", ]) .spawn() .expect("Failed to start server process"); @@ -88,273 +89,174 @@ fn setup_server() -> (ServerProcessGuard, u16) { }; // Give the server a moment to start - std::thread::sleep(Duration::from_millis(100)); + std::thread::sleep(Duration::from_millis(500)); (guard, port) } +async fn cleanup_keys(conn: &mut Connection) { + let keys: Vec = redis::cmd("KEYS").arg("*").query(conn).unwrap(); + if !keys.is_empty() { + for key in keys { + let _: () = redis::cmd("DEL").arg(key).query(conn).unwrap(); + } + } +} + #[tokio::test] async fn all_tests() { let (_server_guard, port) = setup_server(); let mut conn = get_redis_connection(port); // Run all tests using the same connection - cleanup_keys(&mut conn).await; test_basic_ping(&mut conn).await; - cleanup_keys(&mut conn).await; test_string_operations(&mut conn).await; - cleanup_keys(&mut conn).await; test_incr_operations(&mut conn).await; - // cleanup_keys(&mut conn).await; - // test_hash_operations(&mut conn).await; - cleanup_keys(&mut conn).await; + test_hash_operations(&mut conn).await; test_expiration(&mut conn).await; - cleanup_keys(&mut conn).await; test_scan_operations(&mut conn).await; - cleanup_keys(&mut conn).await; test_scan_with_count(&mut conn).await; - cleanup_keys(&mut conn).await; test_hscan_operations(&mut conn).await; - cleanup_keys(&mut conn).await; test_transaction_operations(&mut conn).await; - cleanup_keys(&mut conn).await; test_discard_transaction(&mut conn).await; - cleanup_keys(&mut conn).await; test_type_command(&mut conn).await; - cleanup_keys(&mut conn).await; test_config_commands(&mut conn).await; - cleanup_keys(&mut conn).await; test_info_command(&mut conn).await; - cleanup_keys(&mut conn).await; test_error_handling(&mut conn).await; - - // Clean up keys after all tests - cleanup_keys(&mut conn).await; -} - -async fn cleanup_keys(conn: &mut Connection) { - let keys: Vec = redis::cmd("KEYS").arg("*").query(conn).unwrap(); - if !keys.is_empty() { - let _: () = redis::cmd("DEL").arg(keys).query(conn).unwrap(); - } } async fn test_basic_ping(conn: &mut Connection) { + cleanup_keys(conn).await; let result: String = redis::cmd("PING").query(conn).unwrap(); assert_eq!(result, "PONG"); - + cleanup_keys(conn).await; } async fn test_string_operations(conn: &mut Connection) { - // Test SET + cleanup_keys(conn).await; let _: () = conn.set("key", "value").unwrap(); - - // Test GET let result: String = conn.get("key").unwrap(); assert_eq!(result, "value"); - - // Test GET non-existent key let result: Option = conn.get("noexist").unwrap(); assert_eq!(result, None); - - // Test DEL let deleted: i32 = conn.del("key").unwrap(); assert_eq!(deleted, 1); - - // Test GET after DEL let result: Option = conn.get("key").unwrap(); assert_eq!(result, None); + cleanup_keys(conn).await; } async fn test_incr_operations(conn: &mut Connection) { - // Test INCR on non-existent key + cleanup_keys(conn).await; let result: i32 = redis::cmd("INCR").arg("counter").query(conn).unwrap(); assert_eq!(result, 1); - - // Test INCR on existing key let result: i32 = redis::cmd("INCR").arg("counter").query(conn).unwrap(); assert_eq!(result, 2); - - // Test INCR on string value (should fail) let _: () = conn.set("string", "hello").unwrap(); - let result: Result = redis::cmd("INCR").arg("string").query(conn); + let result: RedisResult = redis::cmd("INCR").arg("string").query(conn); assert!(result.is_err()); + cleanup_keys(conn).await; } async fn test_hash_operations(conn: &mut Connection) { - // Test HSET + cleanup_keys(conn).await; let result: i32 = conn.hset("hash", "field1", "value1").unwrap(); - assert_eq!(result, 1); // 1 new field - - // Test HGET + assert_eq!(result, 1); let result: String = conn.hget("hash", "field1").unwrap(); assert_eq!(result, "value1"); - - // Test HSET multiple fields - let _: () = conn.hset_multiple("hash", &[("field2", "value2"), ("field3", "value3")]).unwrap(); - - // Test HGETALL + let _: () = conn.hset("hash", "field2", "value2").unwrap(); + let _: () = conn.hset("hash", "field3", "value3").unwrap(); let result: std::collections::HashMap = conn.hgetall("hash").unwrap(); assert_eq!(result.len(), 3); assert_eq!(result.get("field1").unwrap(), "value1"); assert_eq!(result.get("field2").unwrap(), "value2"); assert_eq!(result.get("field3").unwrap(), "value3"); - - // Test HLEN let result: i32 = conn.hlen("hash").unwrap(); assert_eq!(result, 3); - - // Test HEXISTS let result: bool = conn.hexists("hash", "field1").unwrap(); assert_eq!(result, true); - let result: bool = conn.hexists("hash", "noexist").unwrap(); assert_eq!(result, false); - - // Test HDEL let result: i32 = conn.hdel("hash", "field1").unwrap(); assert_eq!(result, 1); - - // Test HKEYS let mut result: Vec = conn.hkeys("hash").unwrap(); result.sort(); assert_eq!(result, vec!["field2", "field3"]); - - // Test HVALS let mut result: Vec = conn.hvals("hash").unwrap(); result.sort(); assert_eq!(result, vec!["value2", "value3"]); + cleanup_keys(conn).await; } async fn test_expiration(conn: &mut Connection) { - // Test SETEX (expire in 1 second) + cleanup_keys(conn).await; let _: () = conn.set_ex("expkey", "value", 1).unwrap(); - - // Test TTL let result: i32 = conn.ttl("expkey").unwrap(); - assert!(result == 1 || result == 0); // Should be 1 or 0 seconds - - // Test EXISTS + assert!(result == 1 || result == 0); let result: bool = conn.exists("expkey").unwrap(); assert_eq!(result, true); - - // Wait for expiration sleep(Duration::from_millis(1100)).await; - - // Test GET after expiration let result: Option = conn.get("expkey").unwrap(); assert_eq!(result, None); - - // Test TTL after expiration let result: i32 = conn.ttl("expkey").unwrap(); - assert_eq!(result, -2); // Key doesn't exist - - // Test EXISTS after expiration + assert_eq!(result, -2); let result: bool = conn.exists("expkey").unwrap(); assert_eq!(result, false); + cleanup_keys(conn).await; } async fn test_scan_operations(conn: &mut Connection) { - // Set up test data + cleanup_keys(conn).await; for i in 0..5 { let _: () = conn.set(format!("key{}", i), format!("value{}", i)).unwrap(); } - - // Test SCAN let result: (u64, Vec) = redis::cmd("SCAN") .arg(0) .arg("MATCH") - .arg("*") + .arg("key*") .arg("COUNT") .arg(10) .query(conn) .unwrap(); - let (cursor, keys) = result; - assert_eq!(cursor, 0); // Should complete in one scan + assert_eq!(cursor, 0); assert_eq!(keys.len(), 5); - - // Test KEYS - let mut result: Vec = redis::cmd("KEYS").arg("*").query(conn).unwrap(); - result.sort(); - assert_eq!(result, vec!["key0", "key1", "key2", "key3", "key4"]); + cleanup_keys(conn).await; } async fn test_scan_with_count(conn: &mut Connection) { - // Clean up previous keys - let keys: Vec = redis::cmd("KEYS").arg("scan_key*").query(conn).unwrap(); - if !keys.is_empty() { - let _: () = redis::cmd("DEL").arg(keys).query(conn).unwrap(); - } - - // Set up test data + cleanup_keys(conn).await; for i in 0..15 { let _: () = conn.set(format!("scan_key{}", i), i).unwrap(); } - let mut cursor = 0; let mut all_keys = std::collections::HashSet::new(); - - // First SCAN - let (next_cursor, keys): (u64, Vec) = redis::cmd("SCAN") - .arg(cursor) - .arg("MATCH") - .arg("scan_key*") - .arg("COUNT") - .arg(5) - .query(conn) - .unwrap(); - - assert_ne!(next_cursor, 0); - assert_eq!(keys.len(), 5); - for key in keys { - all_keys.insert(key); + loop { + let (next_cursor, keys): (u64, Vec) = redis::cmd("SCAN") + .arg(cursor) + .arg("MATCH") + .arg("scan_key*") + .arg("COUNT") + .arg(5) + .query(conn) + .unwrap(); + for key in keys { + all_keys.insert(key); + } + cursor = next_cursor; + if cursor == 0 { + break; + } } - cursor = next_cursor; - - // Second SCAN - let (next_cursor, keys): (u64, Vec) = redis::cmd("SCAN") - .arg(cursor) - .arg("MATCH") - .arg("scan_key*") - .arg("COUNT") - .arg(5) - .query(conn) - .unwrap(); - - assert_ne!(next_cursor, 0); - assert_eq!(keys.len(), 5); - for key in keys { - all_keys.insert(key); - } - cursor = next_cursor; - - // Final SCAN - let (next_cursor, keys): (u64, Vec) = redis::cmd("SCAN") - .arg(cursor) - .arg("MATCH") - .arg("scan_key*") - .arg("COUNT") - .arg(5) - .query(conn) - .unwrap(); - - assert_eq!(next_cursor, 0); - assert_eq!(keys.len(), 5); - for key in keys { - all_keys.insert(key); - } - assert_eq!(all_keys.len(), 15); + cleanup_keys(conn).await; } async fn test_hscan_operations(conn: &mut Connection) { - // Set up hash data + cleanup_keys(conn).await; for i in 0..3 { let _: () = conn.hset("testhash", format!("field{}", i), format!("value{}", i)).unwrap(); } - - // Test HSCAN let result: (u64, Vec) = redis::cmd("HSCAN") .arg("testhash") .arg(0) @@ -364,64 +266,56 @@ async fn test_hscan_operations(conn: &mut Connection) { .arg(10) .query(conn) .unwrap(); - let (cursor, fields) = result; - assert_eq!(cursor, 0); // Should complete in one scan - assert_eq!(fields.len(), 6); // 3 field-value pairs = 6 elements + assert_eq!(cursor, 0); + assert_eq!(fields.len(), 6); + cleanup_keys(conn).await; } async fn test_transaction_operations(conn: &mut Connection) { - // Test MULTI/EXEC + cleanup_keys(conn).await; let _: () = redis::cmd("MULTI").query(conn).unwrap(); let _: () = redis::cmd("SET").arg("key1").arg("value1").query(conn).unwrap(); let _: () = redis::cmd("SET").arg("key2").arg("value2").query(conn).unwrap(); let _: Vec = redis::cmd("EXEC").query(conn).unwrap(); - - // Verify commands were executed let result: String = conn.get("key1").unwrap(); assert_eq!(result, "value1"); - let result: String = conn.get("key2").unwrap(); assert_eq!(result, "value2"); + cleanup_keys(conn).await; } async fn test_discard_transaction(conn: &mut Connection) { - // Test MULTI/DISCARD + cleanup_keys(conn).await; let _: () = redis::cmd("MULTI").query(conn).unwrap(); let _: () = redis::cmd("SET").arg("discard").arg("value").query(conn).unwrap(); let _: () = redis::cmd("DISCARD").query(conn).unwrap(); - - // Verify command was not executed let result: Option = conn.get("discard").unwrap(); assert_eq!(result, None); + cleanup_keys(conn).await; } async fn test_type_command(conn: &mut Connection) { - // Test string type + cleanup_keys(conn).await; let _: () = conn.set("string", "value").unwrap(); let result: String = redis::cmd("TYPE").arg("string").query(conn).unwrap(); assert_eq!(result, "string"); - - // Test hash type let _: () = conn.hset("hash", "field", "value").unwrap(); let result: String = redis::cmd("TYPE").arg("hash").query(conn).unwrap(); assert_eq!(result, "hash"); - - // Test non-existent key let result: String = redis::cmd("TYPE").arg("noexist").query(conn).unwrap(); assert_eq!(result, "none"); + cleanup_keys(conn).await; } async fn test_config_commands(conn: &mut Connection) { - // Test CONFIG GET databases + cleanup_keys(conn).await; let result: Vec = redis::cmd("CONFIG") .arg("GET") .arg("databases") .query(conn) .unwrap(); assert_eq!(result, vec!["databases", "16"]); - - // Test CONFIG GET dir let result: Vec = redis::cmd("CONFIG") .arg("GET") .arg("dir") @@ -429,33 +323,28 @@ async fn test_config_commands(conn: &mut Connection) { .unwrap(); assert_eq!(result[0], "dir"); assert!(result[1].contains("/tmp/herodb_test_")); + cleanup_keys(conn).await; } async fn test_info_command(conn: &mut Connection) { - // Test INFO + cleanup_keys(conn).await; let result: String = redis::cmd("INFO").query(conn).unwrap(); assert!(result.contains("redis_version")); - - // Test INFO replication let result: String = redis::cmd("INFO").arg("replication").query(conn).unwrap(); assert!(result.contains("role:master")); + cleanup_keys(conn).await; } async fn test_error_handling(conn: &mut Connection) { - // Test WRONGTYPE error - try to use hash command on string + cleanup_keys(conn).await; let _: () = conn.set("string", "value").unwrap(); - let result: Result = conn.hget("string", "field"); + let result: RedisResult = conn.hget("string", "field"); assert!(result.is_err()); - - // Test unknown command - let result: Result = redis::cmd("UNKNOWN").query(conn); + let result: RedisResult = redis::cmd("UNKNOWN").query(conn); assert!(result.is_err()); - - // Test EXEC without MULTI - let result: Result, _> = redis::cmd("EXEC").query(conn); + let result: RedisResult> = redis::cmd("EXEC").query(conn); assert!(result.is_err()); - - // Test DISCARD without MULTI - let result: Result<(), _> = redis::cmd("DISCARD").query(conn); + let result: RedisResult<()> = redis::cmd("DISCARD").query(conn); assert!(result.is_err()); + cleanup_keys(conn).await; } \ No newline at end of file diff --git a/tests/redis_tests.rs b/tests/redis_tests.rs index c8b9f7b..7ad31bf 100644 --- a/tests/redis_tests.rs +++ b/tests/redis_tests.rs @@ -20,6 +20,7 @@ async fn start_test_server(test_name: &str) -> (Server, u16) { dir: test_dir, port, debug: true, + databases: 16, }; let server = Server::new(option).await; diff --git a/tests/simple_integration_test.rs b/tests/simple_integration_test.rs index bf3e7cc..291b698 100644 --- a/tests/simple_integration_test.rs +++ b/tests/simple_integration_test.rs @@ -22,6 +22,7 @@ async fn start_test_server(test_name: &str) -> (Server, u16) { dir: test_dir, port, debug: true, + databases: 16, }; let server = Server::new(option).await; diff --git a/tests/simple_redis_test.rs b/tests/simple_redis_test.rs index acb41e6..3791779 100644 --- a/tests/simple_redis_test.rs +++ b/tests/simple_redis_test.rs @@ -20,6 +20,7 @@ async fn start_test_server(test_name: &str) -> (Server, u16) { dir: test_dir, port, debug: false, + databases: 16, }; let server = Server::new(option).await;