Skip to main content

kevy_store/
string.rs

1//! `Store` string commands.
2
3use crate::util::{parse_i64, parse_f64, fmt_num};
4use crate::value::{Value, SmallBytes};
5use crate::{Entry, Store, StoreError, deadline_at, now_ns};
6use std::time::Duration;
7
8impl Store {
9    // ---- strings -------------------------------------------------------
10
11    /// `SET` — overwrites any existing value/type. NX/XX guards; clears TTL.
12    /// Takes an owned `Vec` so a >22 B value's allocation is adopted as-is
13    /// (no copy). For callers holding a borrowed slice, prefer
14    /// [`Self::set_slice`] — it skips the `to_vec` entirely for values that
15    /// inline.
16    pub fn set(
17        &mut self,
18        key: &[u8],
19        value: Vec<u8>,
20        expire: Option<Duration>,
21        nx: bool,
22        xx: bool,
23    ) -> bool {
24        self.set_value(key, Value::Str(SmallBytes::from_vec(value)), expire, nx, xx)
25    }
26
27    /// [`Self::set`] for a borrowed value. Values ≤ 22 B store inline in the
28    /// entry — zero allocator traffic, where `set(key, value.to_vec(), …)`
29    /// paid a malloc for the `Vec` and a free when the inline copy dropped
30    /// it (the dominant overwrite-SET pattern). Larger values pay the same
31    /// single allocation either way.
32    pub fn set_slice(
33        &mut self,
34        key: &[u8],
35        value: &[u8],
36        expire: Option<Duration>,
37        nx: bool,
38        xx: bool,
39    ) -> bool {
40        self.set_value(key, Value::Str(SmallBytes::from_slice(value)), expire, nx, xx)
41    }
42
43    fn set_value(
44        &mut self,
45        key: &[u8],
46        new_value: Value,
47        expire: Option<Duration>,
48        nx: bool,
49        xx: bool,
50    ) -> bool {
51        // Clock read only when a TTL is requested. Deadlines stamp from a
52        // fresh clock (`now_ns`), not the coarse cached one.
53        let expire_at = expire.map(|d| deadline_at(now_ns(), d));
54        let key_heap = crate::key_heap_bytes_for(key);
55        // Keeping the match shape (vs `if let … else`) preserves the in-arm
56        // comments that document the NX/XX semantics next to the code they
57        // describe; the auto-suggested if-let-else collapses them awkwardly.
58        #[allow(clippy::single_match_else)]
59        let outcome = match self.live_entry_mut(key) {
60            // Key exists and is live: NX must abort; otherwise overwrite the
61            // value + TTL in place — no `key.to_vec()` (the key is already in
62            // the table, std `insert` would clone it only to drop it). The
63            // weight delta is computed HERE on the `&mut Entry` we already
64            // hold — `reweigh_entry(key)` would re-hash + re-probe the map
65            // for the entry we just mutated (the overwrite-SET hot path).
66            Some(e) => {
67                if nx {
68                    return false;
69                }
70                // SET replaces the TTL (cleared unless this SET carried EX/PX),
71                // so account the expire-set delta from the in-place swap.
72                let had_ttl = e.expire_at_ns.is_some();
73                e.value = new_value;
74                e.expire_at_ns = expire_at.and_then(crate::pack_deadline);
75                let new_w = key_heap + e.value.weight();
76                let delta = new_w as i64 - e.weight() as i64;
77                let ttl_delta = i64::from(e.expire_at_ns.is_some()) - i64::from(had_ttl);
78                e.set_weight(new_w);
79                Ok((delta, ttl_delta))
80            }
81            // Absent (or expired ⇒ already dropped by live_entry_mut): XX aborts.
82            None => {
83                if xx {
84                    return false;
85                }
86                Err(Entry::new(new_value, expire_at))
87            }
88        };
89        match outcome {
90            Ok((delta, ttl_delta)) => {
91                self.apply_weight_delta(delta);
92                self.adjust_expires(ttl_delta);
93            }
94            // New key: insert_entry accounts the expire-set itself.
95            Err(entry) => {
96                self.insert_entry(SmallBytes::from_slice(key), entry);
97            }
98        }
99        true
100    }
101
102    pub fn get(&mut self, key: &[u8]) -> Result<Option<&[u8]>, StoreError> {
103        match self.live_entry(key) {
104            None => Ok(None),
105            Some(e) => match &e.value {
106                Value::Str(v) => Ok(Some(v.as_slice())),
107                _ => Err(StoreError::WrongType),
108            },
109        }
110    }
111
112    /// Read-only `GET`: `&self`, so concurrent readers can run under a shared
113    /// lock (embedded mode's `RwLock` read path). Expiry is checked against the
114    /// coarse cached clock but an expired key is *not* removed here (no `&mut`)
115    /// — the reaper / next write reclaims it; a reader just sees `None`. LRU is
116    /// not touched, so this path is only used when eviction is off
117    /// (`maxmemory == 0`); with eviction, the caller takes the mutating
118    /// [`Self::get`] under an exclusive lock so access still stamps the LRU.
119    pub fn get_shared(&self, key: &[u8]) -> Result<Option<&[u8]>, StoreError> {
120        match self.map.get(key) {
121            None => Ok(None),
122            Some(e) if e.is_expired(self.cached_clock, self.cached_ns) => Ok(None),
123            Some(e) => match &e.value {
124                Value::Str(v) => Ok(Some(v.as_slice())),
125                _ => Err(StoreError::WrongType),
126            },
127        }
128    }
129
130    pub fn strlen(&mut self, key: &[u8]) -> Result<usize, StoreError> {
131        Ok(self.get(key)?.map_or(0, <[u8]>::len))
132    }
133
134    pub fn append(&mut self, key: &[u8], data: &[u8]) -> Result<usize, StoreError> {
135        let outcome = match self.live_entry_mut(key) {
136            Some(e) => match &mut e.value {
137                Value::Str(v) => {
138                    // SmallBytes is immutable; pop out, grow via Vec, re-wrap.
139                    let mut owned = std::mem::take(v).into_vec();
140                    owned.extend_from_slice(data);
141                    let new_len = owned.len();
142                    *v = SmallBytes::from_vec(owned);
143                    AppendOutcome::Reweigh(new_len)
144                }
145                _ => return Err(StoreError::WrongType),
146            },
147            None => AppendOutcome::Insert,
148        };
149        match outcome {
150            AppendOutcome::Reweigh(new_len) => {
151                self.reweigh_entry(key);
152                Ok(new_len)
153            }
154            AppendOutcome::Insert => {
155                self.insert_entry(
156                    SmallBytes::from_slice(key),
157                    Entry::new(Value::Str(SmallBytes::from_slice(data)), None),
158                );
159                Ok(data.len())
160            }
161        }
162    }
163
164    /// `INCRBY` family; preserves any TTL.
165    pub fn incr_by(&mut self, key: &[u8], delta: i64) -> Result<i64, StoreError> {
166        let outcome = match self.live_entry_mut(key) {
167            Some(e) => match &mut e.value {
168                Value::Str(v) => {
169                    let next = parse_i64(v.as_slice())
170                        .ok_or(StoreError::NotInteger)?
171                        .checked_add(delta)
172                        .ok_or(StoreError::Overflow)?;
173                    *v = SmallBytes::from_vec(next.to_string().into_bytes());
174                    IncrOutcome::Reweigh(next)
175                }
176                _ => return Err(StoreError::WrongType),
177            },
178            // Absent/expired ⇒ start from 0; 0 + delta can't overflow i64.
179            None => IncrOutcome::Insert(delta),
180        };
181        match outcome {
182            IncrOutcome::Reweigh(next) => {
183                self.reweigh_entry(key);
184                Ok(next)
185            }
186            IncrOutcome::Insert(next) => {
187                self.insert_entry(
188                    SmallBytes::from_slice(key),
189                    Entry::new(
190                        Value::Str(SmallBytes::from_vec(next.to_string().into_bytes())),
191                        None,
192                    ),
193                );
194                Ok(next)
195            }
196        }
197    }
198
199    /// `GETSET` — set to `val`, return the previous string (WRONGTYPE if the old
200    /// value isn't a string). Clears any TTL, like SET.
201    pub fn getset(&mut self, key: &[u8], val: Vec<u8>) -> Result<Option<Vec<u8>>, StoreError> {
202        let old = match self.live_entry(key) {
203            Some(e) => match &e.value {
204                Value::Str(v) => Some(v.to_vec()),
205                _ => return Err(StoreError::WrongType),
206            },
207            None => None,
208        };
209        self.insert_entry(
210            SmallBytes::from_slice(key),
211            Entry::new(Value::Str(SmallBytes::from_vec(val)), None),
212        );
213        Ok(old)
214    }
215
216    /// `GETDEL` — get then delete (WRONGTYPE if non-string).
217    pub fn getdel(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
218        let is_str = match self.live_entry(key) {
219            None => return Ok(None),
220            Some(e) => matches!(e.value, Value::Str(_)),
221        };
222        if !is_str {
223            return Err(StoreError::WrongType);
224        }
225        match self.remove_entry(key) {
226            Some(Entry {
227                value: Value::Str(v),
228                ..
229            }) => Ok(Some(v.into_vec())),
230            _ => Ok(None),
231        }
232    }
233
234    /// `INCRBYFLOAT` — returns the new value formatted as Redis would. Preserves TTL.
235    pub fn incr_by_float(&mut self, key: &[u8], delta: f64) -> Result<Vec<u8>, StoreError> {
236        let outcome = if let Some(e) = self.live_entry_mut(key) { match &mut e.value {
237            Value::Str(v) => {
238                let next = parse_f64(v.as_slice()).ok_or(StoreError::NotFloat)? + delta;
239                if !next.is_finite() {
240                    return Err(StoreError::NotFloat);
241                }
242                let bytes = fmt_num(next);
243                *v = SmallBytes::from_slice(&bytes);
244                FloatOutcome::Reweigh(bytes)
245            }
246            _ => return Err(StoreError::WrongType),
247        } } else {
248            // Absent/expired ⇒ start from 0.0.
249            if !delta.is_finite() {
250                return Err(StoreError::NotFloat);
251            }
252            FloatOutcome::Insert(fmt_num(delta))
253        };
254        match outcome {
255            FloatOutcome::Reweigh(bytes) => {
256                self.reweigh_entry(key);
257                Ok(bytes)
258            }
259            FloatOutcome::Insert(bytes) => {
260                self.insert_entry(
261                    SmallBytes::from_slice(key),
262                    Entry::new(Value::Str(SmallBytes::from_slice(&bytes)), None),
263                );
264                Ok(bytes)
265            }
266        }
267    }
268}
269
270enum AppendOutcome {
271    Reweigh(usize),
272    Insert,
273}
274
275enum IncrOutcome {
276    Reweigh(i64),
277    Insert(i64),
278}
279
280enum FloatOutcome {
281    Reweigh(Vec<u8>),
282    Insert(Vec<u8>),
283}