use serde::{Serialize, de::DeserializeOwned};
use crate::codec::{Bytes, Codec};
use crate::error::Error;
use crate::keys::matches_pattern_for_internal_use as matches_pattern;
use crate::store::Store;
pub struct HashRef<'a, C: Codec> {
store: &'a Store<C>,
key: &'a str,
}
impl<'a, C: Codec> HashRef<'a, C> {
pub(crate) fn new(store: &'a Store<C>, key: &'a str) -> Self {
Self { store, key }
}
#[inline]
fn enc<T: Serialize>(&self, v: &T) -> Result<Bytes, Error> {
self.store.codec().encode(v)
}
#[inline]
fn enc_field(&self, field: &str) -> Result<Bytes, Error> {
self.store.codec().encode(field)
}
#[inline]
fn dec<T: DeserializeOwned>(&self, b: &[u8]) -> Result<T, Error> {
self.store.codec().decode(b)
}
pub fn hset<T: Serialize>(&self, field: &str, value: &T) -> Result<bool, Error> {
let f = self.enc_field(field)?;
let v = self.enc(value)?;
self.store
.with_hash_mut(self.key, |map| Ok(map.insert(f, v).is_none()))
}
pub fn hget<T: DeserializeOwned>(&self, field: &str) -> Result<Option<T>, Error> {
let f = self.enc_field(field)?;
self.store.with_hash_read(self.key, |opt| {
let Some(map) = opt else {
return Ok(None);
};
let Some(v) = map.get(&f) else {
return Ok(None);
};
Ok(Some(self.dec::<T>(v)?))
})
}
pub fn hmget<T: DeserializeOwned>(&self, fields: &[&str]) -> Result<Vec<Option<T>>, Error> {
let mut out = Vec::with_capacity(fields.len());
for f in fields {
out.push(self.hget::<T>(f)?);
}
Ok(out)
}
pub fn hgetall<T: DeserializeOwned>(&self) -> Result<Vec<(String, T)>, Error> {
self.store.with_hash_read(self.key, |opt| {
let Some(map) = opt else {
return Ok(vec![]);
};
let mut out = Vec::with_capacity(map.len());
for (k, v) in map.iter() {
let field = self.dec::<String>(k)?;
let val = self.dec::<T>(v)?;
out.push((field, val));
}
Ok(out)
})
}
pub fn hdel(&self, field: &str) -> Result<bool, Error> {
let f = self.enc_field(field)?;
self.store
.with_hash_mut(self.key, |map| Ok(map.remove(&f).is_some()))
}
pub fn hexists(&self, field: &str) -> Result<bool, Error> {
let f = self.enc_field(field)?;
self.store.with_hash_read(self.key, |opt| {
let Some(map) = opt else {
return Ok(false);
};
Ok(map.contains_key(&f))
})
}
pub fn hlen(&self) -> Result<usize, Error> {
self.store
.with_hash_read(self.key, |opt| Ok(opt.map(|m| m.len()).unwrap_or(0)))
}
pub fn hkeys(&self) -> Result<Vec<String>, Error> {
self.store.with_hash_read(self.key, |opt| {
let Some(map) = opt else {
return Ok(vec![]);
};
let mut out = Vec::with_capacity(map.len());
for k in map.keys() {
out.push(self.dec::<String>(k)?);
}
Ok(out)
})
}
pub fn hvals<T: DeserializeOwned>(&self) -> Result<Vec<T>, Error> {
self.store.with_hash_read(self.key, |opt| {
let Some(map) = opt else {
return Ok(vec![]);
};
let mut out = Vec::with_capacity(map.len());
for v in map.values() {
out.push(self.dec::<T>(v)?);
}
Ok(out)
})
}
pub fn hincrby(&self, field: &str, delta: i64) -> Result<i64, Error> {
let f = self.enc_field(field)?;
self.store.with_hash_mut(self.key, |map| {
let cur = match map.get(&f) {
None => 0_i64,
Some(b) => self.dec::<i64>(b)?,
};
let next = cur.saturating_add(delta);
map.insert(f, self.enc(&next)?);
Ok(next)
})
}
pub fn hincrbyfloat(&self, field: &str, delta: f64) -> Result<f64, Error> {
let f = self.enc_field(field)?;
self.store.with_hash_mut(self.key, |map| {
let cur = match map.get(&f) {
None => 0_f64,
Some(b) => self.dec::<f64>(b)?,
};
let next = cur + delta;
map.insert(f, self.enc(&next)?);
Ok(next)
})
}
pub fn hscan<T: DeserializeOwned>(
&self,
cursor: crate::keys::ScanCursor,
pattern: Option<&str>,
count: usize,
) -> Result<crate::keys::ScanPage<(String, T)>, Error> {
let items = self.store.with_hash_read(self.key, |opt| {
let Some(map) = opt else {
return Ok(vec![]);
};
let mut out = Vec::with_capacity(map.len());
for (k, v) in map.iter() {
let field = self.dec::<String>(k)?;
let val = self.dec::<T>(v)?;
out.push((field, val));
}
Ok(out)
})?;
let mut filtered = match pattern {
None => items,
Some(pat) => items
.into_iter()
.filter(|(k, _)| matches_pattern(k, pat))
.collect(),
};
if filtered.is_empty() {
return Ok(crate::keys::ScanPage {
cursor: crate::keys::ScanCursor(0),
items: vec![],
});
}
filtered.sort_by(|(ak, _), (bk, _)| ak.cmp(bk));
let start = cursor.0 as usize;
let len = filtered.len();
if start >= len {
return Ok(crate::keys::ScanPage {
cursor: crate::keys::ScanCursor(0),
items: vec![],
});
}
let take = count.max(1);
let end = (start + take).min(len);
let next = if end >= len { 0 } else { end as u64 };
let page_items = filtered.into_iter().skip(start).take(end - start).collect();
Ok(crate::keys::ScanPage {
cursor: crate::keys::ScanCursor(next),
items: page_items,
})
}
pub fn hclear(&self) -> Result<(), Error> {
self.store.with_hash_mut(self.key, |map| {
map.clear();
Ok(())
})
}
}
#[cfg(test)]
mod tests {
use crate::Store;
#[test]
fn hash_basic_ops() {
let store = Store::new();
let h = store.hash("h_basic");
assert_eq!(h.hlen().unwrap(), 0);
assert!(h.hset("a", &1_i64).unwrap());
assert!(!h.hset("a", &2_i64).unwrap()); assert_eq!(h.hlen().unwrap(), 1);
let v: Option<i64> = h.hget("a").unwrap();
assert_eq!(v, Some(2));
assert!(h.hexists("a").unwrap());
assert!(h.hdel("a").unwrap());
assert!(!h.hexists("a").unwrap());
}
#[test]
fn hash_getall_keys_vals() {
let store = Store::new();
let h = store.hash("h_all");
h.hset("a", &1_i64).unwrap();
h.hset("b", &2_i64).unwrap();
let mut keys = h.hkeys().unwrap();
keys.sort();
assert_eq!(keys, vec!["a".to_string(), "b".to_string()]);
let mut vals: Vec<i64> = h.hvals().unwrap();
vals.sort();
assert_eq!(vals, vec![1, 2]);
let all: Vec<(String, i64)> = h.hgetall().unwrap();
assert_eq!(all.len(), 2);
}
#[test]
fn hash_incr() {
let store = Store::new();
let h = store.hash("h_incr");
assert_eq!(h.hincrby("n", 1).unwrap(), 1);
assert_eq!(h.hincrby("n", 5).unwrap(), 6);
assert!((h.hincrbyfloat("f", 0.25).unwrap() - 0.25).abs() < 1e-9);
assert!((h.hincrbyfloat("f", 1.0).unwrap() - 1.25).abs() < 1e-9);
}
#[test]
fn hash_hmget_and_clear() {
let store = Store::new();
let h = store.hash("h_hmget");
h.hset("a", &1_i64).unwrap();
h.hset("b", &2_i64).unwrap();
let v: Vec<Option<i64>> = h.hmget(&["a", "c", "b"]).unwrap();
assert_eq!(v, vec![Some(1), None, Some(2)]);
h.hclear().unwrap();
assert_eq!(h.hlen().unwrap(), 0);
let all: Vec<(String, i64)> = h.hgetall().unwrap();
assert!(all.is_empty());
}
}