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