Skip to main content

kevy_store/
string.rs

1//! `Store` string commands.
2
3use crate::util::*;
4use crate::value::*;
5use crate::{Entry, Store, StoreError};
6use std::time::{Duration, Instant};
7
8impl Store {
9    // ---- strings -------------------------------------------------------
10
11    /// `SET` — overwrites any existing value/type. NX/XX guards; clears TTL.
12    pub fn set(
13        &mut self,
14        key: &[u8],
15        value: Vec<u8>,
16        expire: Option<Duration>,
17        nx: bool,
18        xx: bool,
19    ) -> bool {
20        // Clock read only when a TTL is requested.
21        let expire_at = expire.map(|d| Instant::now() + d);
22        let new_value = Value::Str(SmallBytes::from_vec(value));
23        let pending_insert = match self.live_entry_mut(key) {
24            // Key exists and is live: NX must abort; otherwise overwrite the
25            // value + TTL in place — no `key.to_vec()` (the key is already in
26            // the table, std `insert` would clone it only to drop it).
27            Some(e) => {
28                if nx {
29                    return false;
30                }
31                e.value = new_value;
32                e.expire_at_ns = expire_at.and_then(crate::pack_deadline);
33                None
34            }
35            // Absent (or expired ⇒ already dropped by live_entry_mut): XX aborts.
36            None => {
37                if xx {
38                    return false;
39                }
40                Some(Entry::new(new_value, expire_at))
41            }
42        };
43        match pending_insert {
44            None => self.reweigh_entry(key),
45            Some(entry) => {
46                self.insert_entry(SmallBytes::from_slice(key), entry);
47            }
48        }
49        true
50    }
51
52    pub fn get(&mut self, key: &[u8]) -> Result<Option<&[u8]>, StoreError> {
53        match self.live_entry(key) {
54            None => Ok(None),
55            Some(e) => match &e.value {
56                Value::Str(v) => Ok(Some(v.as_slice())),
57                _ => Err(StoreError::WrongType),
58            },
59        }
60    }
61
62    pub fn strlen(&mut self, key: &[u8]) -> Result<usize, StoreError> {
63        Ok(self.get(key)?.map_or(0, |v| v.len()))
64    }
65
66    pub fn append(&mut self, key: &[u8], data: &[u8]) -> Result<usize, StoreError> {
67        let outcome = match self.live_entry_mut(key) {
68            Some(e) => match &mut e.value {
69                Value::Str(v) => {
70                    // SmallBytes is immutable; pop out, grow via Vec, re-wrap.
71                    let mut owned = std::mem::take(v).into_vec();
72                    owned.extend_from_slice(data);
73                    let new_len = owned.len();
74                    *v = SmallBytes::from_vec(owned);
75                    AppendOutcome::Reweigh(new_len)
76                }
77                _ => return Err(StoreError::WrongType),
78            },
79            None => AppendOutcome::Insert,
80        };
81        match outcome {
82            AppendOutcome::Reweigh(new_len) => {
83                self.reweigh_entry(key);
84                Ok(new_len)
85            }
86            AppendOutcome::Insert => {
87                self.insert_entry(
88                    SmallBytes::from_slice(key),
89                    Entry::new(Value::Str(SmallBytes::from_slice(data)), None),
90                );
91                Ok(data.len())
92            }
93        }
94    }
95
96    /// `INCRBY` family; preserves any TTL.
97    pub fn incr_by(&mut self, key: &[u8], delta: i64) -> Result<i64, StoreError> {
98        let outcome = match self.live_entry_mut(key) {
99            Some(e) => match &mut e.value {
100                Value::Str(v) => {
101                    let next = parse_i64(v.as_slice())
102                        .ok_or(StoreError::NotInteger)?
103                        .checked_add(delta)
104                        .ok_or(StoreError::Overflow)?;
105                    *v = SmallBytes::from_vec(next.to_string().into_bytes());
106                    IncrOutcome::Reweigh(next)
107                }
108                _ => return Err(StoreError::WrongType),
109            },
110            // Absent/expired ⇒ start from 0; 0 + delta can't overflow i64.
111            None => IncrOutcome::Insert(delta),
112        };
113        match outcome {
114            IncrOutcome::Reweigh(next) => {
115                self.reweigh_entry(key);
116                Ok(next)
117            }
118            IncrOutcome::Insert(next) => {
119                self.insert_entry(
120                    SmallBytes::from_slice(key),
121                    Entry::new(
122                        Value::Str(SmallBytes::from_vec(next.to_string().into_bytes())),
123                        None,
124                    ),
125                );
126                Ok(next)
127            }
128        }
129    }
130
131    /// `GETSET` — set to `val`, return the previous string (WRONGTYPE if the old
132    /// value isn't a string). Clears any TTL, like SET.
133    pub fn getset(&mut self, key: &[u8], val: Vec<u8>) -> Result<Option<Vec<u8>>, StoreError> {
134        let old = match self.live_entry(key) {
135            Some(e) => match &e.value {
136                Value::Str(v) => Some(v.to_vec()),
137                _ => return Err(StoreError::WrongType),
138            },
139            None => None,
140        };
141        self.insert_entry(
142            SmallBytes::from_slice(key),
143            Entry::new(Value::Str(SmallBytes::from_vec(val)), None),
144        );
145        Ok(old)
146    }
147
148    /// `GETDEL` — get then delete (WRONGTYPE if non-string).
149    pub fn getdel(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
150        let is_str = match self.live_entry(key) {
151            None => return Ok(None),
152            Some(e) => matches!(e.value, Value::Str(_)),
153        };
154        if !is_str {
155            return Err(StoreError::WrongType);
156        }
157        match self.remove_entry(key) {
158            Some(Entry {
159                value: Value::Str(v),
160                ..
161            }) => Ok(Some(v.into_vec())),
162            _ => Ok(None),
163        }
164    }
165
166    /// `INCRBYFLOAT` — returns the new value formatted as Redis would. Preserves TTL.
167    pub fn incr_by_float(&mut self, key: &[u8], delta: f64) -> Result<Vec<u8>, StoreError> {
168        let outcome = match self.live_entry_mut(key) {
169            Some(e) => match &mut e.value {
170                Value::Str(v) => {
171                    let next = parse_f64(v.as_slice()).ok_or(StoreError::NotFloat)? + delta;
172                    if !next.is_finite() {
173                        return Err(StoreError::NotFloat);
174                    }
175                    let bytes = fmt_num(next);
176                    *v = SmallBytes::from_slice(&bytes);
177                    FloatOutcome::Reweigh(bytes)
178                }
179                _ => return Err(StoreError::WrongType),
180            },
181            None => {
182                // Absent/expired ⇒ start from 0.0.
183                if !delta.is_finite() {
184                    return Err(StoreError::NotFloat);
185                }
186                FloatOutcome::Insert(fmt_num(delta))
187            }
188        };
189        match outcome {
190            FloatOutcome::Reweigh(bytes) => {
191                self.reweigh_entry(key);
192                Ok(bytes)
193            }
194            FloatOutcome::Insert(bytes) => {
195                self.insert_entry(
196                    SmallBytes::from_slice(key),
197                    Entry::new(Value::Str(SmallBytes::from_slice(&bytes)), None),
198                );
199                Ok(bytes)
200            }
201        }
202    }
203}
204
205enum AppendOutcome {
206    Reweigh(usize),
207    Insert,
208}
209
210enum IncrOutcome {
211    Reweigh(i64),
212    Insert(i64),
213}
214
215enum FloatOutcome {
216    Reweigh(Vec<u8>),
217    Insert(Vec<u8>),
218}