Skip to main content

kevy_store/
list.rs

1//! `Store` list write commands. Reads live in `list_read.rs`.
2//!
3//! Three encodings, promoted in order of size: `SmallListInline`
4//! (≤8 tiny elements, in the Value body) → `List(Arc<VecDeque>)` (flat
5//! heap) → `SegList` (per-segment COW past
6//! [`crate::list_seg::SEG_PROMOTE`] elements — a write under a live
7//! snapshot view clones one segment, not the whole value).
8
9#[cfg(not(feature = "std"))]
10use crate::nostd_prelude::*;
11use crate::list_seg::{SEG_PROMOTE, SegListData};
12use crate::small_list::{self, PushResult, SmallListData};
13use crate::util::{norm_index, range_bounds};
14use crate::value::{ListData, SmallBytes, Value, list_item_weight};
15use crate::{Entry, Store, StoreError};
16use alloc::sync::Arc;
17
18impl Store {
19    // ---- lists ---------------------------------------------------------
20
21    /// Borrow the key's flat list mutably; promote inline → heap if
22    /// needed. `create == true` materialises a fresh empty heap list
23    /// when the key is missing (the `lset/lpop/rpop/lrem/ltrim` legacy
24    /// paths). Callers dispatch the `SegList` encoding BEFORE calling
25    /// this — a seg-encoded key never reaches the `make_mut` here.
26    fn list_mut(&mut self, key: &[u8], create: bool) -> Result<Option<&mut ListData>, StoreError> {
27        if self.live_entry_mut(key).is_none() {
28            if !create {
29                return Ok(None);
30            }
31            self.insert_entry(
32                SmallBytes::from_slice(key),
33                Entry::new(Value::List(Arc::default()), None),
34            );
35        }
36        // A.8: see hash.rs::hash_mut — promote out-of-scope, then
37        // re-borrow as the heap variant.
38        let is_inline = matches!(
39            self.map.get(key).map(|e| &e.value),
40            Some(Value::SmallListInline(_))
41        );
42        if is_inline {
43            let promoted = {
44                let e = self.map.get(key).expect("present");
45                if let Value::SmallListInline(s) = &e.value {
46                    small_list::promote(s)
47                } else {
48                    unreachable!()
49                }
50            };
51            self.map.get_mut(key).expect("present").value = Value::List(Arc::new(promoted));
52            self.reweigh_entry(key);
53        }
54        match &mut self.map.get_mut(key).expect("present").value {
55            Value::List(l) => Ok(Some(Arc::make_mut(l))),
56            _ => Err(StoreError::WrongType),
57        }
58    }
59
60    /// Whether `key` currently holds the `SegList` encoding (after lazy
61    /// expiry). The write ops branch on this before the flat path.
62    fn is_seglist(&mut self, key: &[u8]) -> bool {
63        matches!(
64            self.live_entry_mut(key).map(|e| &e.value),
65            Some(Value::SegList(_))
66        )
67    }
68
69    /// Borrow the seg-encoded list mutably. Caller has checked
70    /// [`Self::is_seglist`]; the outer `make_mut` here is the cheap
71    /// pointer-array clone (the per-segment clones happen inside
72    /// `SegListData`'s ops, only on touched segments).
73    fn seglist_mut(&mut self, key: &[u8]) -> &mut SegListData {
74        match &mut self.map.get_mut(key).expect("is_seglist checked").value {
75            Value::SegList(l) => Arc::make_mut(l),
76            _ => unreachable!("is_seglist checked"),
77        }
78    }
79
80    /// A.8: read the key's list slot for LPUSH/RPUSH. `WrongType` on
81    /// non-list. Returns `None` when key is absent — caller creates.
82    fn list_value_for_push(&mut self, key: &[u8]) -> Result<Option<&mut Value>, StoreError> {
83        match self.live_entry_mut(key) {
84            None => Ok(None),
85            Some(e) => match &e.value {
86                Value::List(_) | Value::SegList(_) | Value::SmallListInline(_) => {
87                    Ok(Some(&mut e.value))
88                }
89                _ => Err(StoreError::WrongType),
90            },
91        }
92    }
93
94    /// Remove `key` if it now holds an empty list (any encoding).
95    fn drop_if_empty_list(&mut self, key: &[u8]) {
96        let empty = match self.map.get(key).map(|e| &e.value) {
97            Some(Value::List(l)) => l.is_empty(),
98            Some(Value::SegList(l)) => l.is_empty(),
99            Some(Value::SmallListInline(l)) => l.is_empty(),
100            _ => false,
101        };
102        if empty {
103            self.remove_entry(key);
104        }
105    }
106
107    /// Return the list's length (any encoding). Used by the public
108    /// push functions to compute "new length" after spending entries.
109    fn list_len(&self, key: &[u8]) -> usize {
110        match self.map.get(key).map(|e| &e.value) {
111            Some(Value::List(l)) => l.len(),
112            Some(Value::SegList(l)) => l.len(),
113            Some(Value::SmallListInline(l)) => l.len(),
114            _ => 0,
115        }
116    }
117
118    /// `LPUSH` — prepend each value in turn; returns the new length.
119    pub fn lpush(
120        &mut self,
121        key: &[u8],
122        values: &[&[u8]],
123    ) -> Result<usize, StoreError> {
124        if values.is_empty() {
125            return Ok(self.list_len(key));
126        }
127        let mut delta: i64 = 0;
128        for v in values {
129            delta += self.list_push_one(key, v, /* front= */ true)?;
130        }
131        self.account_delta(key, delta);
132        Ok(self.list_len(key))
133    }
134
135    /// `RPUSH` — append each value; returns the new length.
136    pub fn rpush(
137        &mut self,
138        key: &[u8],
139        values: &[&[u8]],
140    ) -> Result<usize, StoreError> {
141        if values.is_empty() {
142            return Ok(self.list_len(key));
143        }
144        let mut delta: i64 = 0;
145        for v in values {
146            delta += self.list_push_one(key, v, /* front= */ false)?;
147        }
148        self.account_delta(key, delta);
149        Ok(self.list_len(key))
150    }
151
152    /// Push one element, applying the encoding-switch. Returns the
153    /// per-element weight delta (zero for inline / reweighed cases,
154    /// list_item_weight for heap). `front=true` for LPUSH.
155    fn list_push_one(&mut self, key: &[u8], v: &[u8], front: bool) -> Result<i64, StoreError> {
156        if self.list_value_for_push(key)?.is_none() {
157            return Ok(self.list_push_create(key, v));
158        }
159        let slot = self.list_value_for_push(key)?.expect("present and a list");
160        let reweigh = match slot {
161            Value::SmallListInline(s) => {
162                let push = if front { s.try_push_front(v) } else { s.try_push_back(v) };
163                match push {
164                    PushResult::Pushed => return Ok(0),
165                    PushResult::NoRoom => {
166                        let mut promoted = small_list::promote(s);
167                        if front {
168                            promoted.push_front(v.to_vec());
169                        } else {
170                            promoted.push_back(v.to_vec());
171                        }
172                        *slot = Value::List(Arc::new(promoted));
173                        // Reweighed from scratch — caller's delta should
174                        // be 0 for THIS pair (already in the new weight).
175                        true
176                    }
177                }
178            }
179            Value::List(l) if l.len() >= SEG_PROMOTE => {
180                promote_flat_to_seg(slot, v, front);
181                true
182            }
183            Value::List(l) => {
184                let l = Arc::make_mut(l);
185                if front { l.push_front(v.to_vec()) } else { l.push_back(v.to_vec()) }
186                return Ok(list_item_weight(v.len()) as i64);
187            }
188            Value::SegList(l) => {
189                let l = Arc::make_mut(l);
190                if front { l.push_front(v.to_vec()) } else { l.push_back(v.to_vec()) }
191                return Ok(list_item_weight(v.len()) as i64);
192            }
193            _ => return Err(StoreError::WrongType),
194        };
195        if reweigh {
196            self.reweigh_entry(key);
197        }
198        Ok(0)
199    }
200
201    /// Create a fresh entry holding one element. Inline if it fits,
202    /// else heap.
203    fn list_push_create(&mut self, key: &[u8], v: &[u8]) -> i64 {
204        if let Some(inline) = SmallListData::with_one(v) {
205            self.insert_entry(
206                SmallBytes::from_slice(key),
207                Entry::new(Value::SmallListInline(inline), None),
208            );
209            0
210        } else {
211            let mut d = alloc::collections::VecDeque::with_capacity(1);
212            d.push_back(v.to_vec());
213            self.insert_entry(
214                SmallBytes::from_slice(key),
215                Entry::new(Value::List(Arc::new(d)), None),
216            );
217            0
218        }
219    }
220
221    /// `LPOP`/`RPOP` shared body — pop up to `count` from one end.
222    fn list_pop(&mut self, key: &[u8], count: usize, front: bool) -> Result<Vec<Vec<u8>>, StoreError> {
223        // Inline → promote first if there is anything to pop; simpler
224        // than maintaining a second pop path on the packed buffer.
225        if matches!(self.map.get(key).map(|e| &e.value), Some(Value::SmallListInline(_))) {
226            self.promote_list_inline_to_heap(key);
227        }
228        let (out, delta) = {
229            let mut o = Vec::new();
230            let mut d: i64 = 0;
231            if self.is_seglist(key) {
232                let l = self.seglist_mut(key);
233                for _ in 0..count {
234                    let popped = if front { l.pop_front() } else { l.pop_back() };
235                    match popped {
236                        Some(v) => {
237                            d -= list_item_weight(v.len()) as i64;
238                            o.push(v);
239                        }
240                        None => break,
241                    }
242                }
243            } else if let Some(l) = self.list_mut(key, false)? {
244                for _ in 0..count {
245                    let popped = if front { l.pop_front() } else { l.pop_back() };
246                    match popped {
247                        Some(v) => {
248                            d -= list_item_weight(v.len()) as i64;
249                            o.push(v);
250                        }
251                        None => break,
252                    }
253                }
254            }
255            (o, d)
256        };
257        self.account_delta(key, delta);
258        self.drop_if_empty_list(key);
259        Ok(out)
260    }
261
262    /// `LPOP` — pop up to `count` from the head (deleting emptied key).
263    pub fn lpop(&mut self, key: &[u8], count: usize) -> Result<Vec<Vec<u8>>, StoreError> {
264        self.list_pop(key, count, true)
265    }
266
267    /// `RPOP` — pop up to `count` from the tail.
268    pub fn rpop(&mut self, key: &[u8], count: usize) -> Result<Vec<Vec<u8>>, StoreError> {
269        self.list_pop(key, count, false)
270    }
271
272    /// Force-promote an inline list at `key` to its heap variant
273    /// (no-op if already heap or absent). Used by mutating paths that
274    /// only support the heap representations (pop/lrem/lset/ltrim).
275    fn promote_list_inline_to_heap(&mut self, key: &[u8]) {
276        let needs = matches!(
277            self.map.get(key).map(|e| &e.value),
278            Some(Value::SmallListInline(_))
279        );
280        if !needs {
281            return;
282        }
283        let Some(e) = self.map.get_mut(key) else { return };
284        if let Value::SmallListInline(s) = &e.value {
285            let promoted = small_list::promote(s);
286            e.value = Value::List(Arc::new(promoted));
287        }
288        self.reweigh_entry(key);
289    }
290
291    /// `LSET` — errors with `NoSuchKey` / `OutOfRange` like Redis.
292    pub fn lset(&mut self, key: &[u8], idx: i64, val: &[u8]) -> Result<(), StoreError> {
293        self.promote_list_inline_to_heap(key);
294        let delta = if self.is_seglist(key) {
295            let l = self.seglist_mut(key);
296            let i = norm_index(idx, l.len()).ok_or(StoreError::OutOfRange)?;
297            let old = l.set(i, val.to_vec());
298            val.len() as i64 - old.len() as i64
299        } else {
300            let l = self.list_mut(key, false)?.ok_or(StoreError::NoSuchKey)?;
301            let i = norm_index(idx, l.len()).ok_or(StoreError::OutOfRange)?;
302            let old_len = l[i].len() as i64;
303            l[i] = val.to_vec();
304            val.len() as i64 - old_len
305        };
306        self.account_delta(key, delta);
307        Ok(())
308    }
309
310    /// `LINSERT key BEFORE|AFTER pivot value` — insert `value`
311    /// before/after the first occurrence of `pivot` in the list at
312    /// `key`. Returns:
313    /// - new list length on success (`>= 1`);
314    /// - `0` when `key` does not exist;
315    /// - `-1` when `pivot` was not found in the list.
316    ///
317    /// Matches Redis semantics.
318    pub fn linsert(
319        &mut self,
320        key: &[u8],
321        before: bool,
322        pivot: &[u8],
323        val: &[u8],
324    ) -> Result<i64, StoreError> {
325        self.promote_list_inline_to_heap(key);
326        let (result, delta) = if self.is_seglist(key) {
327            let l = self.seglist_mut(key);
328            let Some(idx) = l.position(pivot) else {
329                return Ok(-1);
330            };
331            let insert_at = if before { idx } else { idx + 1 };
332            l.insert(insert_at, val.to_vec());
333            (l.len() as i64, list_item_weight(val.len()) as i64)
334        } else {
335            match self.list_mut(key, false)? {
336                None => return Ok(0),
337                Some(l) => {
338                    let Some(idx) = l.iter().position(|v| v.as_slice() == pivot) else {
339                        return Ok(-1);
340                    };
341                    let insert_at = if before { idx } else { idx + 1 };
342                    l.insert(insert_at, val.to_vec());
343                    (l.len() as i64, list_item_weight(val.len()) as i64)
344                }
345            }
346        };
347        self.account_delta(key, delta);
348        Ok(result)
349    }
350
351    /// `LREM` — remove `count` occurrences (>0 head, <0 tail, 0 all).
352    pub fn lrem(&mut self, key: &[u8], count: i64, val: &[u8]) -> Result<usize, StoreError> {
353        self.promote_list_inline_to_heap(key);
354        let (removed, delta) = if self.is_seglist(key) {
355            self.seglist_mut(key).remove_occurrences(val, count)
356        } else {
357            match self.list_mut(key, false)? {
358                None => (0, 0),
359                Some(l) => flat_lrem(l, count, val),
360            }
361        };
362        self.account_delta(key, delta);
363        self.drop_if_empty_list(key);
364        Ok(removed)
365    }
366
367    /// `LTRIM` — keep only `[start, stop]` (deleting emptied key).
368    pub fn ltrim(&mut self, key: &[u8], start: i64, stop: i64) -> Result<(), StoreError> {
369        self.promote_list_inline_to_heap(key);
370        let delta = if self.is_seglist(key) {
371            let l = self.seglist_mut(key);
372            match range_bounds(start, stop, l.len()) {
373                None => l.clear(),
374                Some((s, e)) => l.trim_to(s, e),
375            }
376        } else if let Some(l) = self.list_mut(key, false)? {
377            match range_bounds(start, stop, l.len()) {
378                None => {
379                    let d = -(l.iter().map(|v| list_item_weight(v.len()) as i64).sum::<i64>());
380                    l.clear();
381                    d
382                }
383                Some((s, e)) => {
384                    let mut d: i64 = 0;
385                    for v in l.iter().skip(e + 1) {
386                        d -= list_item_weight(v.len()) as i64;
387                    }
388                    l.drain(e + 1..);
389                    for v in l.iter().take(s) {
390                        d -= list_item_weight(v.len()) as i64;
391                    }
392                    l.drain(..s);
393                    d
394                }
395            }
396        } else {
397            0
398        };
399        self.account_delta(key, delta);
400        self.drop_if_empty_list(key);
401        Ok(())
402    }
403}
404
405/// Flat list at the promotion threshold: re-encode as segments, then
406/// push. One-time O(SEG_PROMOTE) move (or clone, if a snapshot view
407/// pins the flat Arc right now). Caller reweighs the entry.
408fn promote_flat_to_seg(slot: &mut Value, v: &[u8], front: bool) {
409    let flat = match core::mem::replace(slot, Value::Int(0)) {
410        Value::List(a) => Arc::try_unwrap(a).unwrap_or_else(|a| (*a).clone()),
411        _ => unreachable!("matched List"),
412    };
413    let mut seg = SegListData::from_flat(flat);
414    if front { seg.push_front(v.to_vec()) } else { seg.push_back(v.to_vec()) }
415    *slot = Value::SegList(Arc::new(seg));
416}
417
418/// The flat-list LREM walk (unchanged semantics; hoisted out of the
419/// method so the seg/flat dispatch stays within the fn-LOC cap).
420fn flat_lrem(l: &mut ListData, count: i64, val: &[u8]) -> (usize, i64) {
421    let mut r = 0usize;
422    let mut d: i64 = 0;
423    if count >= 0 {
424        let limit = if count == 0 { usize::MAX } else { count as usize };
425        let mut i = 0;
426        while i < l.len() {
427            if r < limit && l[i] == val {
428                d -= list_item_weight(l[i].len()) as i64;
429                l.remove(i);
430                r += 1;
431            } else {
432                i += 1;
433            }
434        }
435    } else {
436        let limit = (-count) as usize;
437        let mut i = l.len();
438        while i > 0 {
439            i -= 1;
440            if r < limit && l[i] == val {
441                d -= list_item_weight(l[i].len()) as i64;
442                l.remove(i);
443                r += 1;
444            }
445        }
446    }
447    (r, d)
448}