Skip to main content

kevy_embedded/
ops_hash_ttl.rs

1//! Embedded hash field-TTL facades (Redis 7.4 family).
2//!
3//! AOF: deadline writes log the canonical absolute `HPEXPIREAT`
4//! frame regardless of which facade set them (relative forms convert
5//! before logging — replay can't re-anchor); `hpersist` logs itself.
6
7use crate::KevyResult;
8use std::time::Duration;
9
10use kevy_store::{HExpireCode, HExpireCond, now_unix_ms};
11
12use crate::store::ensure_writable;
13use crate::store::{Store, commit_write, store_err};
14
15impl Store {
16    /// `HEXPIRE` — set a relative per-field TTL. One Redis code per
17    /// field (`-2` missing, `0` condition failed, `1` set, `2` deleted).
18    pub fn hexpire(
19        &self,
20        key: &[u8],
21        fields: &[&[u8]],
22        ttl: Duration,
23        cond: HExpireCond,
24    ) -> KevyResult<Vec<HExpireCode>> {
25        let deadline = now_unix_ms().saturating_add(ttl.as_millis() as u64);
26        self.hpexpire_at(key, fields, deadline, cond)
27    }
28
29    /// `HPEXPIREAT` — absolute unix-ms per-field deadline (the
30    /// canonical form; also what the AOF carries).
31    pub fn hpexpire_at(
32        &self,
33        key: &[u8],
34        fields: &[&[u8]],
35        deadline_ms: u64,
36        cond: HExpireCond,
37    ) -> KevyResult<Vec<HExpireCode>> {
38        ensure_writable(self)?;
39        let mut g = self.wshard(key);
40        let codes = g.store.hexpire_at(key, fields, deadline_ms, cond).map_err(store_err)?;
41        // the record names only the fields that changed (set, or deleted by
42        // a past deadline): it carries no condition, so a field the
43        // condition refused must not appear in it
44        let changed: Vec<&[u8]> = fields
45            .iter()
46            .zip(&codes)
47            .filter(|&(_, &c)| c == 1 || c == 2)
48            .map(|(f, _)| *f)
49            .collect();
50        if !changed.is_empty() {
51            let ms = deadline_ms.to_string();
52            let n = changed.len().to_string();
53            let mut argv: Vec<&[u8]> = Vec::with_capacity(5 + changed.len());
54            argv.push(b"HPEXPIREAT");
55            argv.push(key);
56            argv.push(ms.as_bytes());
57            argv.push(b"FIELDS");
58            argv.push(n.as_bytes());
59            argv.extend(changed);
60            commit_write(&mut g, &argv)?;
61        }
62        Ok(codes)
63    }
64
65    /// `HTTL` — remaining **seconds** per field (`-2` missing, `-1` no TTL).
66    ///
67    /// Rounded to the nearest second, as the wire verb and Redis both do. For
68    /// the unrounded value use [`Self::hpttl`]. Before 4.0 this method carried the
69    /// HTTL name and returned milliseconds, which is a thousand-fold error
70    /// waiting to happen in any caller that trusted the name.
71    pub fn httl(&self, key: &[u8], fields: &[&[u8]]) -> KevyResult<Vec<i64>> {
72        Ok(self
73            .hpttl(key, fields)?
74            .into_iter()
75            .map(|ms| if ms >= 0 { (ms + 500) / 1000 } else { ms })
76            .collect())
77    }
78
79    /// `HPTTL` — remaining **milliseconds** per field (`-2` missing, `-1` no
80    /// TTL).
81    pub fn hpttl(&self, key: &[u8], fields: &[&[u8]]) -> KevyResult<Vec<i64>> {
82        let mut g = self.wshard(key);
83        g.store.hpttl(key, fields).map_err(store_err)
84    }
85
86    /// `HPERSIST` — clear per-field TTLs (`-2` missing, `-1` had no
87    /// TTL, `1` cleared).
88    pub fn hpersist(&self, key: &[u8], fields: &[&[u8]]) -> KevyResult<Vec<HExpireCode>> {
89        ensure_writable(self)?;
90        let mut g = self.wshard(key);
91        let codes = g.store.hpersist(key, fields).map_err(store_err)?;
92        if codes.contains(&1) {
93            let n = fields.len().to_string();
94            let mut argv: Vec<&[u8]> = Vec::with_capacity(4 + fields.len());
95            argv.push(b"HPERSIST");
96            argv.push(key);
97            argv.push(b"FIELDS");
98            argv.push(n.as_bytes());
99            argv.extend(fields.iter().copied());
100            commit_write(&mut g, &argv)?;
101        }
102        Ok(codes)
103    }
104}