Skip to main content

kevy_embedded/
ops_hash_ttl.rs

1//! v2.4 — 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 std::io;
8use std::time::Duration;
9
10use kevy_store::{HExpireCode, HExpireCond, now_unix_ms};
11
12#[cfg(not(target_arch = "wasm32"))]
13use crate::replica_glue::ensure_writable;
14use crate::store::{Store, commit_write, store_err};
15
16#[cfg(target_arch = "wasm32")]
17fn ensure_writable(_s: &Store) -> io::Result<()> {
18    Ok(())
19}
20
21impl Store {
22    /// `HEXPIRE` — set a relative per-field TTL. One Redis code per
23    /// field (`-2` missing, `0` condition failed, `1` set, `2` deleted).
24    pub fn hexpire(
25        &self,
26        key: &[u8],
27        fields: &[&[u8]],
28        ttl: Duration,
29        cond: HExpireCond,
30    ) -> io::Result<Vec<HExpireCode>> {
31        let deadline = now_unix_ms().saturating_add(ttl.as_millis() as u64);
32        self.hpexpire_at(key, fields, deadline, cond)
33    }
34
35    /// `HPEXPIREAT` — absolute unix-ms per-field deadline (the
36    /// canonical form; also what the AOF carries).
37    pub fn hpexpire_at(
38        &self,
39        key: &[u8],
40        fields: &[&[u8]],
41        deadline_ms: u64,
42        cond: HExpireCond,
43    ) -> io::Result<Vec<HExpireCode>> {
44        ensure_writable(self)?;
45        let mut g = self.wshard(key);
46        let codes = g.store.hexpire_at(key, fields, deadline_ms, cond).map_err(store_err)?;
47        // Log only when something changed (set or immediate-delete).
48        if codes.iter().any(|&c| c == 1 || c == 2) {
49            let ms = deadline_ms.to_string();
50            let n = fields.len().to_string();
51            let mut argv: Vec<&[u8]> = Vec::with_capacity(5 + fields.len());
52            argv.push(b"HPEXPIREAT");
53            argv.push(key);
54            argv.push(ms.as_bytes());
55            argv.push(b"FIELDS");
56            argv.push(n.as_bytes());
57            argv.extend(fields.iter().copied());
58            commit_write(&mut g, &argv)?;
59        }
60        Ok(codes)
61    }
62
63    /// `HTTL` — remaining ms per field (`-2` missing, `-1` no TTL).
64    pub fn httl(&self, key: &[u8], fields: &[&[u8]]) -> io::Result<Vec<i64>> {
65        let mut g = self.wshard(key);
66        g.store.httl(key, fields).map_err(store_err)
67    }
68
69    /// `HPERSIST` — clear per-field TTLs (`-2` missing, `-1` had no
70    /// TTL, `1` cleared).
71    pub fn hpersist(&self, key: &[u8], fields: &[&[u8]]) -> io::Result<Vec<HExpireCode>> {
72        ensure_writable(self)?;
73        let mut g = self.wshard(key);
74        let codes = g.store.hpersist(key, fields).map_err(store_err)?;
75        if codes.contains(&1) {
76            let n = fields.len().to_string();
77            let mut argv: Vec<&[u8]> = Vec::with_capacity(4 + fields.len());
78            argv.push(b"HPERSIST");
79            argv.push(key);
80            argv.push(b"FIELDS");
81            argv.push(n.as_bytes());
82            argv.extend(fields.iter().copied());
83            commit_write(&mut g, &argv)?;
84        }
85        Ok(codes)
86    }
87}