use std::time::{Duration, Instant};
use crate::codec::Codec;
use crate::entry::ValueType;
use crate::store::Store;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScanCursor(pub u64);
#[derive(Debug, Clone)]
pub struct ScanPage<T> {
pub cursor: ScanCursor,
pub items: Vec<T>,
}
pub struct KeysOps<'a, C: Codec> {
store: &'a Store<C>,
}
impl<'a, C: Codec> KeysOps<'a, C> {
pub(crate) fn new(store: &'a Store<C>) -> Self {
Self { store }
}
#[must_use]
pub fn exists(&self, key: &str) -> bool {
self.store.purge_if_expired(key);
self.store.contains_key(key)
}
pub fn del(&self, key: &str) -> bool {
self.store.remove_entry(key)
}
pub fn expire(&self, key: &str, seconds: u64) -> bool {
self.pexpire(key, seconds.saturating_mul(1000))
}
pub fn pexpire(&self, key: &str, ms: u64) -> bool {
self.store.purge_if_expired(key);
if self.store.get_entry_meta(key).is_none() {
return false;
}
let when = Instant::now() + Duration::from_millis(ms);
self.store.set_expire_at(key, Some(when))
}
#[must_use]
pub fn ttl(&self, key: &str) -> Option<i64> {
self.store.purge_if_expired(key);
let meta = self.store.get_entry_meta(key)?;
match meta.expire_at {
None => Some(-1),
Some(t) => {
let now = Instant::now();
if t <= now {
self.store.remove_entry(key);
None
} else {
Some((t - now).as_secs() as i64)
}
}
}
}
#[must_use]
pub fn pttl(&self, key: &str) -> Option<i64> {
self.store.purge_if_expired(key);
let meta = self.store.get_entry_meta(key)?;
match meta.expire_at {
None => Some(-1),
Some(t) => {
let now = Instant::now();
if t <= now {
self.store.remove_entry(key);
None
} else {
Some((t - now).as_millis() as i64)
}
}
}
}
pub fn persist(&self, key: &str) -> bool {
self.store.purge_if_expired(key);
self.store.set_expire_at(key, None)
}
#[must_use]
pub fn r#type(&self, key: &str) -> Option<ValueType> {
self.store.purge_if_expired(key);
self.store.get_entry_meta(key).map(|m| m.value_type)
}
pub fn rename(&self, from: &str, to: &str) -> Result<(), crate::error::Error> {
self.store.purge_if_expired(from);
self.store.purge_if_expired(to);
self.store
.rename_internal(from, to, false)
.map(|_| ())
}
pub fn renamenx(&self, from: &str, to: &str) -> Result<bool, crate::error::Error> {
self.store.purge_if_expired(from);
self.store.purge_if_expired(to);
self.store.rename_internal(from, to, true)
}
#[must_use]
pub fn scan(
&self,
cursor: ScanCursor,
pattern: Option<&str>,
count: usize,
) -> ScanPage<String> {
let mut keys = self.store.snapshot_keys();
keys.sort();
let filtered: Vec<String> = match pattern {
None => keys,
Some(pat) => keys
.into_iter()
.filter(|k| matches_pattern(k, pat))
.collect(),
};
if filtered.is_empty() {
return ScanPage {
cursor: ScanCursor(0),
items: vec![],
};
}
let start = cursor.0 as usize;
if start >= filtered.len() {
return ScanPage {
cursor: ScanCursor(0),
items: vec![],
};
}
let take = count.max(1);
let end = (start + take).min(filtered.len());
let items = filtered[start..end].to_vec();
let next = if end >= filtered.len() { 0 } else { end as u64 };
ScanPage {
cursor: ScanCursor(next),
items,
}
}
pub fn iter_keys(&self, pattern: Option<&str>) -> impl Iterator<Item = String> {
let mut keys = self.store.snapshot_keys();
keys.sort();
let v: Vec<String> = match pattern {
None => keys,
Some(pat) => keys
.into_iter()
.filter(|k| matches_pattern(k, pat))
.collect(),
};
v.into_iter()
}
}
fn matches_pattern(s: &str, pat: &str) -> bool {
if pat == "*" {
return true;
}
let parts: Vec<&str> = pat.split('*').collect();
match parts.as_slice() {
[one] => s == *one,
[prefix, ""] => s.starts_with(prefix),
["", suffix] => s.ends_with(suffix),
["", mid, ""] => s.contains(mid),
_ => {
let mut idx = 0usize;
for part in parts.into_iter().filter(|p| !p.is_empty()) {
if let Some(pos) = s[idx..].find(part) {
idx += pos + part.len();
} else {
return false;
}
}
true
}
}
}
pub(crate) fn matches_pattern_for_internal_use(s: &str, pat: &str) -> bool {
matches_pattern(s, pat)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Store;
#[test]
fn keys_exists_del_type() {
let store = Store::new();
assert!(!store.keys().exists("k"));
store.kv().set("k", &123_i32).unwrap();
assert!(store.keys().exists("k"));
assert_eq!(store.keys().r#type("k"), Some(ValueType::String));
assert!(store.keys().del("k"));
assert!(!store.keys().exists("k"));
assert_eq!(store.keys().r#type("k"), None);
}
#[test]
fn keys_ttl_persist_expire() {
let store = Store::new();
store.kv().set("k", &"v").unwrap();
assert_eq!(store.keys().ttl("k"), Some(-1));
assert!(store.keys().expire("k", 1));
let t = store.keys().ttl("k").unwrap();
assert!(t >= 0);
assert!(store.keys().persist("k"));
assert_eq!(store.keys().ttl("k"), Some(-1));
}
#[test]
fn keys_rename_renamenx() {
let store = Store::new();
store.kv().set("a", &1_i32).unwrap();
store.keys().rename("a", "b").unwrap();
let v: Option<i32> = store.kv().get("b").unwrap();
assert_eq!(v, Some(1));
store.kv().set("x", &1_i32).unwrap();
store.kv().set("y", &2_i32).unwrap();
let ok = store.keys().renamenx("x", "y").unwrap();
assert!(!ok); }
#[test]
fn keys_scan_iter_keys() {
let store = Store::new();
store.kv().set("user:1", &1_i32).unwrap();
store.kv().set("user:2", &2_i32).unwrap();
store.kv().set("other", &0_i32).unwrap();
let page = store.keys().scan(ScanCursor(0), Some("user:*"), 10);
assert_eq!(page.cursor, ScanCursor(0));
assert_eq!(page.items.len(), 2);
let all: Vec<String> = store.keys().iter_keys(Some("*")).collect();
assert!(all.len() >= 3);
}
#[test]
fn keys_edge_cases() {
let store = Store::new();
assert!(store.keys().rename("missing", "x").is_err());
store.kv().set("a", &1_i64).unwrap();
store.kv().set("b", &2_i64).unwrap();
assert!(!store.keys().renamenx("a", "b").unwrap());
assert!(!store.keys().expire("missing", 1));
assert_eq!(store.keys().ttl("a"), Some(-1));
assert!(store.keys().persist("a"));
let p = store.keys().scan(ScanCursor(10_000), None, 10);
assert_eq!(p.cursor.0, 0);
}
}