Skip to main content

kevy_embedded/
ops_bitmap.rs

1//! Bitmap reads, writes, and aggregates: `GETBIT` / `SETBIT` /
2//! `BITCOUNT` / `BITPOS` / `BITOP` / `GETRANGE` / `SETRANGE`.
3//!
4//! Strings act as bit arrays addressed MSB-first within each byte,
5//! matching Redis semantics.
6
7use crate::{KevyError, KevyResult};
8use kevy_store::BitOp;
9
10use crate::store::ensure_writable;
11use crate::store::{Store, commit_write, store_err};
12
13impl Store {
14    /// `GETBIT key offset` — return the bit at `offset` (MSB-first).
15    /// `0` for missing key or past-end.
16    pub fn getbit(&self, key: &[u8], offset: u64) -> KevyResult<u8> {
17        self.wshard(key).store.getbit(key, offset).map_err(store_err)
18    }
19
20    /// `SETBIT key offset value` — set the bit at `offset` to
21    /// `value` (0 or 1). Extends the underlying string with zero
22    /// padding as needed. Returns the PREVIOUS bit value.
23    pub fn setbit(&self, key: &[u8], offset: u64, value: u8) -> KevyResult<u8> {
24        ensure_writable(self)?;
25        let mut g = self.wshard(key);
26        let prev = g.store.setbit(key, offset, value).map_err(store_err)?;
27        let off_str = format!("{offset}");
28        let val_str = format!("{value}");
29        commit_write(&mut g, &[b"SETBIT", key, off_str.as_bytes(), val_str.as_bytes()])?;
30        Ok(prev)
31    }
32
33    /// `BITCOUNT key [start end]` — count set bits over the
34    /// optional byte-offset range (inclusive, negatives-from-tail
35    /// like Redis). `None` for `range` = whole string.
36    pub fn bitcount(&self, key: &[u8], range: Option<(i64, i64)>) -> KevyResult<u64> {
37        self.wshard(key).store.bitcount(key, range).map_err(store_err)
38    }
39
40    /// `BITPOS key bit [start [end]]` — find first bit equal to
41    /// `bit` (0 or 1) in the optional byte range. Returns `None`
42    /// when not found (Redis would reply `:-1`).
43    pub fn bitpos(
44        &self,
45        key: &[u8],
46        bit: u8,
47        range: Option<(i64, i64)>,
48    ) -> KevyResult<Option<u64>> {
49        self.wshard(key).store.bitpos(key, bit, range).map_err(store_err)
50    }
51
52    /// `GETRANGE key start end` — substring with Redis negative
53    /// indexing; `[start, end]` inclusive.
54    pub fn getrange(&self, key: &[u8], start: i64, end: i64) -> KevyResult<Vec<u8>> {
55        self.wshard(key).store.getrange(key, start, end).map_err(store_err)
56    }
57
58    /// `SETRANGE key offset value` — overwrite bytes at `offset`;
59    /// extends with zero padding if past current length. Returns
60    /// the new total length.
61    pub fn setrange(&self, key: &[u8], offset: u64, value: &[u8]) -> KevyResult<usize> {
62        ensure_writable(self)?;
63        let mut g = self.wshard(key);
64        let new_len = g.store.setrange(key, offset, value).map_err(store_err)?;
65        let off_str = format!("{offset}");
66        commit_write(&mut g, &[b"SETRANGE", key, off_str.as_bytes(), value])?;
67        Ok(new_len)
68    }
69
70    /// `BITOP AND|OR|XOR|NOT destkey srckey [srckey ...]` — bitwise
71    /// op across N source keys, stored at `destkey`. Returns the
72    /// destination string length (= longest source length, with
73    /// shorter sources zero-padded). For `Not`, exactly one source
74    /// key (additional ones are rejected).
75    pub fn bitop(&self, op: BitOp, dst: &[u8], srcs: &[&[u8]]) -> KevyResult<usize> {
76        ensure_writable(self)?;
77        if srcs.is_empty() {
78            return Ok(0);
79        }
80        if matches!(op, BitOp::Not) && srcs.len() != 1 {
81            return Err(KevyError::InvalidInput("BITOP NOT takes exactly one source key".into()));
82        }
83        // Read each source (own each as Vec<u8>) — set-algebra style.
84        let mut srcs_bytes: Vec<Vec<u8>> = Vec::with_capacity(srcs.len());
85        for k in srcs {
86            let v = self.get(k)?.unwrap_or_default();
87            srcs_bytes.push(v);
88        }
89        let max_len = srcs_bytes.iter().map(Vec::len).max().unwrap_or(0);
90        if max_len == 0 {
91            // Empty result — delete dst.
92            self.del(&[dst])?;
93            return Ok(0);
94        }
95        let out = kevy_store::bitop_combine(op, &srcs_bytes, max_len);
96        // Write dst.
97        self.set(dst, &out)?;
98        Ok(max_len)
99    }
100
101    /// `TIME` — `(unix_seconds, microseconds)` tuple. Useful for
102    /// time-based embedded logic + tracing.
103    pub fn time(&self) -> (u64, u32) {
104        let now =
105            std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
106        (now.as_secs(), now.subsec_micros())
107    }
108}