Skip to content

Commit 8e7ccd5

Browse files
committed
feature: support hgetall command
1 parent 9460f28 commit 8e7ccd5

File tree

4 files changed

+141
-2
lines changed

4 files changed

+141
-2
lines changed

src/backend/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ impl BackendInner {
6464
let hmap = self.hmap.entry(key).or_default();
6565
hmap.insert(field, value);
6666
}
67+
68+
pub fn hget_all(&self, key: &str) -> Option<DashMap<String, RespFrame>> {
69+
self.hmap.get(key).map(|v| v.clone())
70+
}
6771
}
6872

6973
#[cfg(test)]

src/cmd/hgetall.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use crate::backend::Backend;
2+
use crate::cmd::{extract_args, validate_command, CommandError, CommandExecutor, RESP_EMPTY};
3+
use crate::{BulkString, RespArray, RespFrame};
4+
5+
#[derive(Debug)]
6+
pub struct HGetAll {
7+
key: String,
8+
}
9+
10+
impl CommandExecutor for HGetAll {
11+
fn execute(self, backend: &Backend) -> Result<RespFrame, CommandError> {
12+
match backend.hget_all(&self.key) {
13+
Some(map) => {
14+
let mut ret: Vec<RespFrame> = Vec::with_capacity(map.len() * 2);
15+
map.iter().all(|item| {
16+
ret.push(BulkString::from(item.key().to_string()).into());
17+
ret.push(item.value().clone());
18+
true
19+
});
20+
Ok(RespArray::new(ret).into())
21+
}
22+
None => Ok(RESP_EMPTY.clone()),
23+
}
24+
}
25+
}
26+
27+
// hgetall key
28+
// *2\r\n$7\r\nhgetall\r\n$3\r\nkey\r\n
29+
impl TryFrom<RespArray> for HGetAll {
30+
type Error = CommandError;
31+
32+
fn try_from(value: RespArray) -> Result<Self, Self::Error> {
33+
validate_command(&value, &["hgetall"], 1)?;
34+
35+
let mut args = extract_args(value, 1)?.into_iter();
36+
match args.next() {
37+
Some(RespFrame::BulkString(key)) => Ok(HGetAll {
38+
key: String::from_utf8(key.to_vec())?,
39+
}),
40+
_ => Err(CommandError::InvalidArgs("Invalid arguments".to_string())),
41+
}
42+
}
43+
}
44+
45+
#[cfg(test)]
46+
mod tests {
47+
use super::*;
48+
use crate::cmd::hset::HSet;
49+
use crate::RespDecode;
50+
use anyhow::Result;
51+
use std::collections::BTreeMap;
52+
53+
#[test]
54+
fn test_hgetall_command() -> Result<()> {
55+
let mut cmd = bytes::BytesMut::from(&b"*2\r\n$7\r\nhgetall\r\n$3\r\nkey\r\n"[..]);
56+
let cmd = RespArray::decode(&mut cmd)?;
57+
let hget_all = HGetAll::try_from(cmd)?;
58+
assert_eq!(hget_all.key, "key");
59+
60+
Ok(())
61+
}
62+
63+
#[test]
64+
fn test_hset_and_hgetall_command() -> Result<()> {
65+
let backend = Backend::new();
66+
67+
let mut hset = bytes::BytesMut::from(
68+
&b"*4\r\n$4\r\nhset\r\n$3\r\nkey\r\n$5\r\nhello\r\n$5\r\nworld\r\n"[..],
69+
);
70+
let hset = RespArray::decode(&mut hset)?;
71+
let hset = HSet::try_from(hset)?;
72+
hset.execute(&backend)?;
73+
74+
let mut hset = bytes::BytesMut::from(
75+
&b"*4\r\n$4\r\nhset\r\n$3\r\nkey\r\n$2\r\nhi\r\n$4\r\nrust\r\n"[..],
76+
);
77+
let hset = RespArray::decode(&mut hset)?;
78+
let hset = HSet::try_from(hset)?;
79+
hset.execute(&backend)?;
80+
81+
let mut hgetall = bytes::BytesMut::from(&b"*2\r\n$7\r\nhgetall\r\n$3\r\nkey\r\n"[..]);
82+
let hgetall = RespArray::decode(&mut hgetall)?;
83+
let hgetall = HGetAll::try_from(hgetall)?;
84+
let resp = hgetall.execute(&backend)?;
85+
86+
let resp = match resp {
87+
RespFrame::Array(array) => array,
88+
_ => panic!("Expected Array"),
89+
};
90+
assert_eq!(resp.len() % 2, 0);
91+
92+
// 排序
93+
let mut sorted_map = BTreeMap::new();
94+
resp.iter().enumerate().step_by(2).for_each(|(i, key)| {
95+
let key = match key {
96+
RespFrame::BulkString(key) => String::from_utf8_lossy(key.as_slice()),
97+
_ => panic!("Expected BulkString"),
98+
};
99+
let value = &resp[i + 1];
100+
sorted_map.insert(key.to_string(), value.clone());
101+
});
102+
103+
let mut sorted_resp = Vec::with_capacity(resp.len());
104+
sorted_map.iter().for_each(|(key, value)| {
105+
sorted_resp.push(BulkString::from(key.to_string()).into());
106+
sorted_resp.push(value.clone());
107+
});
108+
109+
assert_eq!(
110+
sorted_resp,
111+
vec![
112+
BulkString::from("hello").into(),
113+
BulkString::from("world").into(),
114+
BulkString::from("hi").into(),
115+
BulkString::from("rust").into()
116+
]
117+
);
118+
119+
Ok(())
120+
}
121+
}

src/cmd/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::backend::Backend;
22
use crate::cmd::get::Get;
33
use crate::cmd::hget::HGet;
4+
use crate::cmd::hgetall::HGetAll;
45
use crate::cmd::hset::HSet;
56
use crate::cmd::set::Set;
67
use crate::{RespArray, RespFrame, RespNull};
@@ -10,6 +11,7 @@ use thiserror::Error;
1011

1112
mod get;
1213
mod hget;
14+
mod hgetall;
1315
mod hset;
1416
mod set;
1517

@@ -29,7 +31,7 @@ pub enum Command {
2931
Set(Set),
3032
HGet(HGet),
3133
HSet(HSet),
32-
// HSetAll(hset_all::HSetAll),
34+
HSetAll(HGetAll),
3335
Unrecognized(Unrecognized),
3436
}
3537

@@ -71,7 +73,7 @@ impl TryFrom<RespFrame> for Command {
7173
b"set" => Ok(Command::Set(Set::try_from(frame)?)),
7274
b"hget" => Ok(Command::HGet(HGet::try_from(frame)?)),
7375
b"hset" => Ok(Command::HSet(HSet::try_from(frame)?)),
74-
// b"hset_all" => Ok(Command::HSetAll(hset_all::HSetAll::new(cmd))),
76+
b"hgetall" => Ok(Command::HSetAll(HGetAll::try_from(frame)?)),
7577
_ => Ok(Command::Unrecognized(Unrecognized)),
7678
},
7779
_ => Err(CommandError::InvalidArgs("Invalid arguments".to_string())),

src/resp/bulk_string.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ impl RespDecode for BulkString {
3131
}
3232
}
3333

34+
impl From<String> for BulkString {
35+
fn from(s: String) -> Self {
36+
BulkString::new(s.into_bytes())
37+
}
38+
}
39+
40+
impl From<&str> for BulkString {
41+
fn from(s: &str) -> Self {
42+
BulkString::new(s.as_bytes())
43+
}
44+
}
45+
3446
impl From<&[u8]> for BulkString {
3547
fn from(data: &[u8]) -> Self {
3648
BulkString(data.to_vec())

0 commit comments

Comments
 (0)