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