use serde::{Serialize, de::DeserializeOwned};
use std::time::{Duration, Instant};
use crate::codec::{Bytes, Codec};
use crate::entry::{Entry, Meta, StringEntry, ValueType};
use crate::error::Error;
use crate::store::Store;
pub struct KvOps<'a, C: Codec> {
store: &'a Store<C>,
}
impl<'a, C: Codec> KvOps<'a, C> {
pub(crate) fn new(store: &'a Store<C>) -> Self {
Self { store }
}
pub fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<(), Error> {
let bytes = self.store.codec().encode(value)?;
self.store.put_string_entry(key, bytes);
Ok(())
}
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, Error> {
self.store.purge_if_expired(key);
let Some(entry) = self.store.get_entry(key) else {
return Ok(None);
};
match entry {
Entry::String(se) => Ok(Some(self.store.codec().decode::<T>(&se.bytes)?)),
other => Err(Error::WrongType {
expected: ValueType::String.as_str(),
got: other.value_type().as_str(),
}),
}
}
pub fn mget<T: DeserializeOwned>(&self, keys: &[&str]) -> Result<Vec<Option<T>>, Error> {
let mut out = Vec::with_capacity(keys.len());
for k in keys {
out.push(self.get::<T>(k)?);
}
Ok(out)
}
pub fn mset<T: Serialize>(&self, pairs: &[(&str, T)]) -> Result<(), Error> {
for (k, v) in pairs {
self.set(k, v)?;
}
Ok(())
}
pub fn setnx<T: Serialize>(&self, key: &str, value: &T) -> Result<bool, Error> {
let bytes = self.store.codec().encode(value)?;
self.store.purge_if_expired(key);
let ok = self.store.with_map_write(|m| {
if m.contains_key(key) {
return false;
}
m.insert(
key.to_string(),
Entry::String(StringEntry {
meta: Meta::new(ValueType::String),
bytes: bytes.clone(),
}),
);
true
});
Ok(ok)
}
pub fn setxx<T: Serialize>(&self, key: &str, value: &T) -> Result<bool, Error> {
enum SetXx {
Updated,
Missing,
WrongType(ValueType),
}
let bytes = self.store.codec().encode(value)?;
self.store.purge_if_expired(key);
let outcome = self.store.with_map_write(|m| {
let Some(existing) = m.get_mut(key) else {
return SetXx::Missing;
};
match existing {
Entry::String(se) => {
se.bytes = bytes.clone();
SetXx::Updated
}
other => SetXx::WrongType(other.value_type()),
}
});
match outcome {
SetXx::Updated => Ok(true),
SetXx::Missing => Ok(false),
SetXx::WrongType(got) => Err(Error::WrongType {
expected: ValueType::String.as_str(),
got: got.as_str(),
}),
}
}
pub fn getdel<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, Error> {
self.store.purge_if_expired(key);
let removed = self.store.with_map_write(|m| m.remove(key));
let Some(entry) = removed else {
return Ok(None);
};
match entry {
Entry::String(se) => Ok(Some(self.store.codec().decode::<T>(&se.bytes)?)),
other => Err(Error::WrongType {
expected: ValueType::String.as_str(),
got: other.value_type().as_str(),
}),
}
}
pub fn getex<T: DeserializeOwned>(&self, key: &str, seconds: u64) -> Result<Option<T>, Error> {
let v = self.get::<T>(key)?;
if v.is_some() {
let when = Instant::now() + Duration::from_secs(seconds);
self.store.set_expire_at(key, Some(when));
}
Ok(v)
}
pub fn incr(&self, key: &str) -> Result<i64, Error> {
self.incrby(key, 1)
}
pub fn decr(&self, key: &str) -> Result<i64, Error> {
self.decrby(key, 1)
}
pub fn incrby(&self, key: &str, delta: i64) -> Result<i64, Error> {
self.store.purge_if_expired(key);
let new_val = self.store.with_map_write(|m| match m.get_mut(key) {
None => {
let v = delta;
let bytes = self.store.codec().encode(&v)?;
m.insert(
key.to_string(),
Entry::String(StringEntry {
meta: Meta::new(ValueType::String),
bytes,
}),
);
Ok::<_, Error>(v)
}
Some(entry) => match entry {
Entry::String(se) => {
let base = match self.store.codec().decode::<i64>(&se.bytes) {
Ok(n) => n,
Err(_) => se.bytes.len() as i64,
};
let v = base.saturating_add(delta);
se.bytes = self.store.codec().encode(&v)?;
Ok(v)
}
other => Err(Error::WrongType {
expected: ValueType::String.as_str(),
got: other.value_type().as_str(),
}),
},
})?;
Ok(new_val)
}
pub fn decrby(&self, key: &str, delta: i64) -> Result<i64, Error> {
self.incrby(key, -delta)
}
pub fn incrbyfloat(&self, key: &str, delta: f64) -> Result<f64, Error> {
self.store.purge_if_expired(key);
let new_val = self.store.with_map_write(|m| match m.get_mut(key) {
None => {
let v = delta;
let bytes = self.store.codec().encode(&v)?;
m.insert(
key.to_string(),
Entry::String(StringEntry {
meta: Meta::new(ValueType::String),
bytes,
}),
);
Ok::<_, Error>(v)
}
Some(entry) => match entry {
Entry::String(se) => {
let cur: f64 = self.store.codec().decode(&se.bytes)?;
let v = cur + delta;
se.bytes = self.store.codec().encode(&v)?;
Ok(v)
}
other => Err(Error::WrongType {
expected: ValueType::String.as_str(),
got: other.value_type().as_str(),
}),
},
})?;
Ok(new_val)
}
pub fn append(&self, key: &str, suffix: &str) -> Result<usize, Error> {
self.store.purge_if_expired(key);
let suffix_bytes = suffix.as_bytes();
let new_len = self.store.with_map_write(|m| match m.get_mut(key) {
None => {
let mut v = Vec::with_capacity(suffix_bytes.len());
v.extend_from_slice(suffix_bytes);
let bytes: Bytes = v.into();
let len = bytes.len();
m.insert(
key.to_string(),
Entry::String(StringEntry {
meta: Meta::new(ValueType::String),
bytes,
}),
);
Ok::<_, Error>(len)
}
Some(entry) => match entry {
Entry::String(se) => {
let mut v = se.bytes.to_vec();
v.extend_from_slice(suffix_bytes);
se.bytes = Bytes::from(v);
Ok(se.bytes.len())
}
other => Err(Error::WrongType {
expected: ValueType::String.as_str(),
got: other.value_type().as_str(),
}),
},
})?;
Ok(new_len)
}
pub fn strlen(&self, key: &str) -> Result<usize, Error> {
self.store.purge_if_expired(key);
let len = self.store.with_map_read(|m| match m.get(key) {
None => Ok::<_, Error>(0),
Some(entry) => match entry {
Entry::String(se) => Ok(se.bytes.len()),
other => Err(Error::WrongType {
expected: ValueType::String.as_str(),
got: other.value_type().as_str(),
}),
},
})?;
Ok(len)
}
pub fn del(&self, key: &str) -> bool {
self.store.remove_entry(key)
}
}
#[cfg(test)]
mod tests {
use crate::{Error, Store};
#[test]
fn kv_mget_mset() {
let store = Store::new();
store.kv().mset(&[("a", 1_i64), ("b", 2_i64)]).unwrap();
let v: Vec<Option<i64>> = store.kv().mget(&["a", "b", "c"]).unwrap();
assert_eq!(v, vec![Some(1), Some(2), None]);
}
#[test]
fn kv_setnx_setxx() {
let store = Store::new();
assert!(store.kv().setnx("k", &1_i64).unwrap());
assert!(!store.kv().setnx("k", &2_i64).unwrap());
assert!(store.kv().setxx("k", &3_i64).unwrap());
let v: Option<i64> = store.kv().get("k").unwrap();
assert_eq!(v, Some(3));
assert!(!store.kv().setxx("missing", &1_i64).unwrap());
}
#[test]
fn kv_getdel_getex() {
let store = Store::new();
store.kv().set("k", &"v").unwrap();
let v: Option<String> = store.kv().getdel("k").unwrap();
assert_eq!(v.as_deref(), Some("v"));
let v2: Option<String> = store.kv().get("k").unwrap();
assert_eq!(v2, None);
store.kv().set("t", &"x").unwrap();
let x: Option<String> = store.kv().getex("t", 1).unwrap();
assert_eq!(x.as_deref(), Some("x"));
}
#[test]
fn kv_incr_family() {
let store = Store::new();
assert_eq!(store.kv().incr("n").unwrap(), 1);
store.kv().set("n", &10_i64).unwrap();
assert_eq!(store.kv().incrby("n", 5).unwrap(), 15);
assert_eq!(store.kv().decr("n").unwrap(), 14);
assert_eq!(store.kv().decrby("n", 4).unwrap(), 10);
}
#[test]
fn kv_incrbyfloat() {
let store = Store::new();
store.kv().set("f", &1.0_f64).unwrap();
let v = store.kv().incrbyfloat("f", 0.25).unwrap();
assert!((v - 1.25).abs() < 1e-9);
}
#[test]
fn kv_append_strlen() {
let store = Store::new();
assert_eq!(store.kv().strlen("s").unwrap(), 0);
assert_eq!(store.kv().append("s", "hi").unwrap(), 2);
assert_eq!(store.kv().append("s", "!!!").unwrap(), 5);
assert_eq!(store.kv().strlen("s").unwrap(), 5);
}
fn assert_wrong_type(err: Error, expected: &'static str) {
match err {
Error::WrongType {
expected: e,
got: _,
} => assert_eq!(e, expected),
other => panic!("expected WrongType, got: {:?}", other),
}
}
#[test]
fn kv_wrong_type_from_other_structures() {
let store = Store::new();
store.hash("k").hset("a", &1_i64).unwrap();
let err = store.kv().get::<i64>("k").unwrap_err();
assert_wrong_type(err, "string");
store.set("s").sadd(&"x").unwrap();
let err = store.kv().get::<String>("s").unwrap_err();
assert_wrong_type(err, "string");
}
#[test]
fn kv_incr_on_non_number_overwrites_to_numeric() {
let store = Store::new();
let s = "not-a-number".to_string();
store.kv().set("n", &s).unwrap();
let v = store.kv().incr("n").unwrap();
assert_eq!(v, (s.len() as i64) + 1);
let cur: Option<i64> = store.kv().get("n").unwrap();
assert_eq!(cur, Some(v));
}
}