Skip to main content

kevy_store/
set.rs

1//! `Store` set write commands. Reads live in `set_read.rs`.
2//!
3//! Three encodings, promoted in order of size: `SmallSetInline` (≤8
4//! tiny members, in the Value body) → `Set(Arc<KevySet>)` (flat heap) →
5//! `SegSet` (bucket-sharded COW past [`crate::seg_map::HS_PROMOTE`]
6//! members — a write under a live snapshot view clones one bucket, not
7//! the whole value).
8
9#[cfg(not(feature = "std"))]
10use crate::nostd_prelude::*;
11use crate::seg_map::{HS_PROMOTE, SegMap};
12use crate::small_set::{AddResult, SmallSetData, promote};
13use crate::value::{SetData, SmallBytes, Value, set_member_weight};
14use crate::{Entry, Store, StoreError};
15use alloc::sync::Arc;
16
17impl Store {
18    // ---- sets ----------------------------------------------------------
19
20    /// Borrow the value at `key` for mutation. Returns `None` if the key
21    /// is absent (and the caller creates) or `WrongType` on a non-set.
22    fn set_value_mut(&mut self, key: &[u8]) -> Result<Option<&mut Value>, StoreError> {
23        match self.live_entry_mut(key) {
24            None => Ok(None),
25            Some(e) => match &e.value {
26                Value::Set(_) | Value::SegSet(_) | Value::SmallSetInline(_) => {
27                    Ok(Some(&mut e.value))
28                }
29                _ => Err(StoreError::WrongType),
30            },
31        }
32    }
33
34    fn drop_if_empty_set(&mut self, key: &[u8]) {
35        let empty = match self.map.get(key).map(|e| &e.value) {
36            Some(Value::Set(s)) => s.is_empty(),
37            Some(Value::SegSet(s)) => s.is_empty(),
38            Some(Value::SmallSetInline(s)) => s.is_empty(),
39            _ => false,
40        };
41        if empty {
42            self.remove_entry(key);
43        }
44    }
45
46    /// `SADD` — returns the count of newly-added members.
47    pub fn sadd(&mut self, key: &[u8], members: &[&[u8]]) -> Result<usize, StoreError> {
48        if members.is_empty() {
49            return Ok(0);
50        }
51        let mut added = 0usize;
52        let mut delta: i64 = 0;
53        for m in members {
54            match self.sadd_one(key, m)? {
55                SaddOutcome::AddedInline => added += 1,
56                SaddOutcome::AddedHeap(w) => {
57                    added += 1;
58                    delta += w;
59                }
60                SaddOutcome::AlreadyPresent => {}
61            }
62        }
63        self.account_delta(key, delta);
64        Ok(added)
65    }
66
67    /// Insert one member; encapsulates the encoding-switch decision.
68    fn sadd_one(&mut self, key: &[u8], m: &[u8]) -> Result<SaddOutcome, StoreError> {
69        if self.set_value_mut(key)?.is_none() {
70            return Ok(self.sadd_create(key, m));
71        }
72        let v = self.set_value_mut(key)?.expect("present and a set type");
73        match v {
74            Value::SmallSetInline(s) => match s.try_add(m) {
75                AddResult::Added => Ok(SaddOutcome::AddedInline),
76                AddResult::AlreadyPresent => Ok(SaddOutcome::AlreadyPresent),
77                AddResult::NoRoom => {
78                    let outcome = promote_inline_set_and_add(v, m);
79                    self.reweigh_entry(key);
80                    Ok(outcome)
81                }
82            },
83            // Flat set at the threshold: shard, then add. One-time
84            // O(HS_PROMOTE) re-bucket (or clone, if a view pins it now).
85            Value::Set(s) if s.len() >= HS_PROMOTE => {
86                let added = promote_flat_set_to_seg(v, m);
87                self.reweigh_entry(key);
88                // Reweighed from scratch — swallow the per-member delta.
89                if added { Ok(SaddOutcome::AddedHeap(0)) } else { Ok(SaddOutcome::AlreadyPresent) }
90            }
91            Value::Set(s) => {
92                let smb = SmallBytes::from_slice(m);
93                let w = set_member_weight(&smb) as i64;
94                if Arc::make_mut(s).insert(smb) {
95                    Ok(SaddOutcome::AddedHeap(w))
96                } else {
97                    Ok(SaddOutcome::AlreadyPresent)
98                }
99            }
100            Value::SegSet(s) => {
101                let smb = SmallBytes::from_slice(m);
102                let w = set_member_weight(&smb) as i64;
103                if Arc::make_mut(s).insert(smb, ()).is_none() {
104                    Ok(SaddOutcome::AddedHeap(w))
105                } else {
106                    Ok(SaddOutcome::AlreadyPresent)
107                }
108            }
109            _ => Err(StoreError::WrongType),
110        }
111    }
112
113    /// Create a fresh entry for `key` holding one member.
114    fn sadd_create(&mut self, key: &[u8], m: &[u8]) -> SaddOutcome {
115        if let Some(inline) = SmallSetData::with_one(m) {
116            self.insert_entry(
117                SmallBytes::from_slice(key),
118                Entry::new(Value::SmallSetInline(inline), None),
119            );
120        } else {
121            let smb = SmallBytes::from_slice(m);
122            let mut s = SetData::with_capacity(1);
123            s.insert(smb);
124            self.insert_entry(
125                SmallBytes::from_slice(key),
126                Entry::new(Value::Set(Arc::new(s)), None),
127            );
128        }
129        SaddOutcome::AddedInline
130    }
131
132    /// `SREM` — returns the count removed (deleting an emptied key).
133    pub fn srem(&mut self, key: &[u8], members: &[&[u8]]) -> Result<usize, StoreError> {
134        let (removed, delta) = {
135            let mut r = 0usize;
136            let mut d: i64 = 0;
137            if let Some(v) = self.set_value_mut(key)? {
138                match v {
139                    Value::SmallSetInline(s) => {
140                        for m in members {
141                            if s.try_remove(m) {
142                                r += 1;
143                            }
144                        }
145                    }
146                    Value::Set(s) => {
147                        let set_mut = Arc::make_mut(s);
148                        for m in members {
149                            if set_mut.remove(*m) {
150                                r += 1;
151                                d -= set_member_weight(&SmallBytes::from_slice(m)) as i64;
152                            }
153                        }
154                    }
155                    Value::SegSet(s) => {
156                        let set_mut = Arc::make_mut(s);
157                        for m in members {
158                            if set_mut.remove(m).is_some() {
159                                r += 1;
160                                d -= set_member_weight(&SmallBytes::from_slice(m)) as i64;
161                            }
162                        }
163                    }
164                    _ => return Err(StoreError::WrongType),
165                }
166            }
167            (r, d)
168        };
169        self.account_delta(key, delta);
170        self.drop_if_empty_set(key);
171        Ok(removed)
172    }
173
174    /// `SPOP key count` — remove and return up to `count` arbitrary
175    /// members. Each draw starts at a random slot and takes the first
176    /// occupied one — O(1) expected, Redis's `dictGetRandomKey` shape
177    /// (sharded sets weight the bucket pick by length first).
178    pub fn spop(&mut self, key: &[u8], count: usize) -> Result<Vec<Vec<u8>>, StoreError> {
179        let mut draws: Vec<u64> = (0..count).map(|_| self.rng.next_u64()).collect();
180        let (out, delta) = {
181            let mut o: Vec<Vec<u8>> = Vec::new();
182            let mut d: i64 = 0;
183            if let Some(v) = self.set_value_mut(key)? {
184                match v {
185                    Value::SmallSetInline(s) => {
186                        let mut all: Vec<Vec<u8>> = s.iter_slices().map(<[u8]>::to_vec).collect();
187                        let k = shuffle_prefix(&mut all, count, &mut draws);
188                        all.truncate(k);
189                        for m in &all {
190                            s.try_remove(m.as_slice());
191                        }
192                        o = all;
193                    }
194                    Value::Set(s) => {
195                        (o, d) = flat_spop_draws(Arc::make_mut(s), &draws, count);
196                    }
197                    Value::SegSet(s) => {
198                        (o, d) = seg_spop_draws(Arc::make_mut(s), &draws, count);
199                    }
200                    _ => return Err(StoreError::WrongType),
201                }
202            }
203            (o, d)
204        };
205        self.account_delta(key, delta);
206        self.drop_if_empty_set(key);
207        Ok(out)
208    }
209}
210
211/// Inline set out of room: promote to KevySet, then insert the
212/// spilling member. Caller reweighs the entry.
213fn promote_inline_set_and_add(v: &mut Value, m: &[u8]) -> SaddOutcome {
214    let Value::SmallSetInline(s) = v else { unreachable!("matched inline") };
215    let mut promoted = promote(s);
216    let smb = SmallBytes::from_slice(m);
217    let w = set_member_weight(&smb) as i64;
218    let inserted = promoted.insert(smb);
219    debug_assert!(inserted, "promote re-inserts existing inline");
220    *v = Value::Set(Arc::new(promoted));
221    if inserted { SaddOutcome::AddedHeap(w) } else { SaddOutcome::AlreadyPresent }
222}
223
224/// Flat set at the promotion threshold: re-bucket, then add `m`.
225/// Returns whether `m` was newly added. Caller reweighs the entry.
226fn promote_flat_set_to_seg(v: &mut Value, m: &[u8]) -> bool {
227    let Value::Set(s) = v else { unreachable!("matched Set") };
228    let flat = Arc::try_unwrap(core::mem::take(s)).unwrap_or_else(|a| (*a).clone());
229    let mut seg: SegMap<()> = SegMap::default();
230    for member in flat.iter() {
231        seg.insert(member.clone(), ());
232    }
233    let added = seg.insert(SmallBytes::from_slice(m), ()).is_none();
234    *v = Value::SegSet(Arc::new(seg));
235    added
236}
237
238/// The SPOP draw loop over a flat set. Returns `(popped, delta)`.
239fn flat_spop_draws(set_mut: &mut SetData, draws: &[u64], count: usize) -> (Vec<Vec<u8>>, i64) {
240    let (mut o, mut d) = (Vec::new(), 0i64);
241    for slot in draws.iter().take(count) {
242        if set_mut.is_empty() {
243            break;
244        }
245        let Some(m) =
246            set_mut.iter_from_slot(*slot as usize).next().map(kevy_bytes::SmallBytes::to_vec)
247        else {
248            break;
249        };
250        if set_mut.remove(m.as_slice()) {
251            d -= set_member_weight(&SmallBytes::from_slice(&m)) as i64;
252        }
253        o.push(m);
254    }
255    (o, d)
256}
257
258/// The SPOP draw loop over a sharded set (weighted-bucket random).
259fn seg_spop_draws(set_mut: &mut SegMap<()>, draws: &[u64], count: usize) -> (Vec<Vec<u8>>, i64) {
260    let (mut o, mut d) = (Vec::new(), 0i64);
261    for draw in draws.iter().take(count) {
262        if set_mut.is_empty() {
263            break;
264        }
265        let Some(m) = set_mut.rand_entry(*draw).map(|(m, ())| m.to_vec()) else {
266            break;
267        };
268        if set_mut.remove(m.as_slice()).is_some() {
269            d -= set_member_weight(&SmallBytes::from_slice(&m)) as i64;
270        }
271        o.push(m);
272    }
273    (o, d)
274}
275
276/// Per-member result for the inner [`Store::sadd_one`] step.
277enum SaddOutcome {
278    AddedInline,
279    AddedHeap(i64),
280    AlreadyPresent,
281}
282
283/// Fisher-Yates over the first `k` positions, using pre-drawn
284/// randomness (drawn BEFORE the value borrow — `self.rng` is
285/// unreachable inside).
286pub(crate) fn shuffle_prefix<T>(items: &mut [T], k: usize, draws: &mut Vec<u64>) -> usize {
287    let n = items.len();
288    let k = k.min(n);
289    for i in 0..k {
290        let span = (n - i) as u64;
291        let d = draws.pop().unwrap_or(i as u64);
292        items.swap(i, i + crate::rng::below(d, span) as usize);
293    }
294    k
295}