Skip to main content

ember_core/keyspace/
set.rs

1use super::*;
2
3impl Keyspace {
4    /// Adds one or more members to a set.
5    ///
6    /// Creates the set if the key doesn't exist. Returns the number of
7    /// new members added (existing members don't count).
8    pub fn sadd(&mut self, key: &str, members: &[String]) -> Result<usize, WriteError> {
9        if members.is_empty() {
10            return Ok(0);
11        }
12
13        self.remove_if_expired(key);
14
15        let is_new = self.ensure_collection_type(key, |v| matches!(v, Value::Set(_)))?;
16
17        let member_increase: usize = members
18            .iter()
19            .map(|m| m.len() + memory::HASHSET_MEMBER_OVERHEAD)
20            .sum();
21        self.reserve_memory(is_new, key, memory::HASHSET_BASE_OVERHEAD, member_increase)?;
22
23        if is_new {
24            self.insert_empty(key, Value::Set(Box::default()));
25        }
26
27        let track_access = self.track_access;
28        let added = self
29            .track_size(key, |entry| {
30                let Value::Set(ref mut set) = entry.value else {
31                    unreachable!("type verified by ensure_collection_type");
32                };
33                let mut added = 0;
34                for member in members {
35                    if set.insert(member.clone()) {
36                        added += 1;
37                    }
38                }
39                entry.touch(track_access);
40                added
41            })
42            .unwrap_or(0);
43
44        Ok(added)
45    }
46
47    /// Removes one or more members from a set.
48    ///
49    /// Returns the number of members that were actually removed.
50    pub fn srem(&mut self, key: &str, members: &[String]) -> Result<usize, WrongType> {
51        if self.remove_if_expired(key) {
52            return Ok(0);
53        }
54
55        let Some(entry) = self.entries.get_mut(key) else {
56            return Ok(0);
57        };
58        if !matches!(entry.value, Value::Set(_)) {
59            return Err(WrongType);
60        }
61
62        let old_entry_size = entry.entry_size(key);
63
64        let mut removed = 0;
65        let mut removed_bytes: usize = 0;
66        let is_empty = if let Value::Set(ref mut set) = entry.value {
67            for member in members {
68                if set.remove(member) {
69                    removed_bytes += member.len() + memory::HASHSET_MEMBER_OVERHEAD;
70                    removed += 1;
71                }
72            }
73            set.is_empty()
74        } else {
75            false
76        };
77        if removed > 0 {
78            self.bump_version(key);
79        }
80
81        self.cleanup_after_remove(key, old_entry_size, is_empty, removed_bytes);
82
83        Ok(removed)
84    }
85
86    /// Returns all members of a set.
87    pub fn smembers(&mut self, key: &str) -> Result<Vec<String>, WrongType> {
88        let Some(entry) = self.get_live_entry(key) else {
89            return Ok(vec![]);
90        };
91        match &entry.value {
92            Value::Set(set) => Ok(set.iter().cloned().collect()),
93            _ => Err(WrongType),
94        }
95    }
96
97    /// Checks if a member exists in a set.
98    pub fn sismember(&mut self, key: &str, member: &str) -> Result<bool, WrongType> {
99        let Some(entry) = self.get_live_entry(key) else {
100            return Ok(false);
101        };
102        match &entry.value {
103            Value::Set(set) => Ok(set.contains(member)),
104            _ => Err(WrongType),
105        }
106    }
107
108    /// Incrementally iterates members of a set.
109    ///
110    /// Returns the next cursor and a batch of members. A returned cursor
111    /// of `0` means the iteration is complete. Pattern matching (MATCH)
112    /// filters on member names.
113    pub fn scan_set(
114        &mut self,
115        key: &str,
116        cursor: u64,
117        count: usize,
118        pattern: Option<&str>,
119    ) -> Result<(u64, Vec<String>), WrongType> {
120        let Some(entry) = self.get_live_entry(key) else {
121            return Ok((0, vec![]));
122        };
123        let Value::Set(ref set) = entry.value else {
124            return Err(WrongType);
125        };
126
127        let target = if count == 0 { 10 } else { count };
128        let compiled = pattern.map(GlobPattern::new);
129        let mut result = Vec::with_capacity(target);
130        let mut pos = 0u64;
131        let mut done = true;
132
133        for member in set.iter() {
134            if pos < cursor {
135                pos += 1;
136                continue;
137            }
138            if let Some(ref pat) = compiled {
139                if !pat.matches(member) {
140                    pos += 1;
141                    continue;
142                }
143            }
144            result.push(member.clone());
145            pos += 1;
146            if result.len() >= target {
147                done = false;
148                break;
149            }
150        }
151
152        Ok(if done { (0, result) } else { (pos, result) })
153    }
154
155    /// Returns the union of all given sets.
156    ///
157    /// Keys that don't exist are treated as empty sets. Returns an error
158    /// only if any key holds a non-set type.
159    pub fn sunion(&mut self, keys: &[String]) -> Result<Vec<String>, WrongType> {
160        let mut result = std::collections::HashSet::new();
161        for key in keys {
162            self.remove_if_expired(key);
163            match self.entries.get_mut(key.as_str()) {
164                None => {}
165                Some(entry) => match &entry.value {
166                    Value::Set(set) => {
167                        result.extend(set.iter().cloned());
168                        entry.touch(self.track_access);
169                    }
170                    _ => return Err(WrongType),
171                },
172            }
173        }
174        Ok(result.into_iter().collect())
175    }
176
177    /// Returns the intersection of all given sets.
178    ///
179    /// If any key doesn't exist, the result is empty. Returns an error
180    /// only if any key holds a non-set type.
181    pub fn sinter(&mut self, keys: &[String]) -> Result<Vec<String>, WrongType> {
182        if keys.is_empty() {
183            return Ok(vec![]);
184        }
185
186        // check types and find missing keys first
187        for key in keys {
188            self.remove_if_expired(key);
189            match self.entries.get(key.as_str()) {
190                None => return Ok(vec![]), // any missing key → empty intersection
191                Some(entry) => {
192                    if !matches!(&entry.value, Value::Set(_)) {
193                        return Err(WrongType);
194                    }
195                }
196            }
197        }
198
199        // start with the first set, intersect with the rest
200        let entry = self
201            .entries
202            .get_mut(keys[0].as_str())
203            .expect("checked above");
204        let Value::Set(ref base) = entry.value else {
205            unreachable!("type checked above");
206        };
207        let candidates: Vec<String> = base.iter().cloned().collect();
208        entry.touch(self.track_access);
209
210        let result: Vec<String> = candidates
211            .into_iter()
212            .filter(|member| {
213                keys[1..].iter().all(|key| {
214                    self.entries
215                        .get(key.as_str())
216                        .and_then(|e| match &e.value {
217                            Value::Set(s) => Some(s.contains(member)),
218                            _ => None,
219                        })
220                        .unwrap_or(false)
221                })
222            })
223            .collect();
224
225        // touch remaining keys
226        for key in &keys[1..] {
227            if let Some(entry) = self.entries.get_mut(key.as_str()) {
228                entry.touch(self.track_access);
229            }
230        }
231
232        Ok(result)
233    }
234
235    /// Returns members of the first set that are not in any of the other sets.
236    ///
237    /// If the first key doesn't exist, the result is empty. Returns an error
238    /// only if any key holds a non-set type.
239    pub fn sdiff(&mut self, keys: &[String]) -> Result<Vec<String>, WrongType> {
240        if keys.is_empty() {
241            return Ok(vec![]);
242        }
243
244        // type-check all keys
245        for key in keys {
246            self.remove_if_expired(key);
247            if let Some(entry) = self.entries.get(key.as_str()) {
248                if !matches!(&entry.value, Value::Set(_)) {
249                    return Err(WrongType);
250                }
251            }
252        }
253
254        let Some(first_entry) = self.entries.get_mut(keys[0].as_str()) else {
255            return Ok(vec![]);
256        };
257        let Value::Set(ref base) = first_entry.value else {
258            unreachable!("type checked above");
259        };
260        let candidates: Vec<String> = base.iter().cloned().collect();
261        first_entry.touch(self.track_access);
262
263        let result: Vec<String> = candidates
264            .into_iter()
265            .filter(|member| {
266                !keys[1..].iter().any(|key| {
267                    self.entries
268                        .get(key.as_str())
269                        .and_then(|e| match &e.value {
270                            Value::Set(s) => Some(s.contains(member)),
271                            _ => None,
272                        })
273                        .unwrap_or(false)
274                })
275            })
276            .collect();
277
278        // touch remaining keys
279        for key in &keys[1..] {
280            if let Some(entry) = self.entries.get_mut(key.as_str()) {
281                entry.touch(self.track_access);
282            }
283        }
284
285        Ok(result)
286    }
287
288    /// Stores the union of all source sets into `dest`.
289    ///
290    /// Overwrites the destination if it already exists. Returns the
291    /// cardinality and stored members (for AOF persistence).
292    pub fn sunionstore(
293        &mut self,
294        dest: &str,
295        keys: &[String],
296    ) -> Result<(usize, Vec<String>), WriteError> {
297        let members = self.sunion(keys).map_err(|_| WriteError::WrongType)?;
298        self.store_set_result(dest, members)
299    }
300
301    /// Stores the intersection of all source sets into `dest`.
302    pub fn sinterstore(
303        &mut self,
304        dest: &str,
305        keys: &[String],
306    ) -> Result<(usize, Vec<String>), WriteError> {
307        let members = self.sinter(keys).map_err(|_| WriteError::WrongType)?;
308        self.store_set_result(dest, members)
309    }
310
311    /// Stores the difference of sets (first minus the rest) into `dest`.
312    pub fn sdiffstore(
313        &mut self,
314        dest: &str,
315        keys: &[String],
316    ) -> Result<(usize, Vec<String>), WriteError> {
317        let members = self.sdiff(keys).map_err(|_| WriteError::WrongType)?;
318        self.store_set_result(dest, members)
319    }
320
321    /// Writes a computed set result to `dest`, replacing any existing key.
322    /// Returns the count and the stored members (for AOF).
323    fn store_set_result(
324        &mut self,
325        dest: &str,
326        members: Vec<String>,
327    ) -> Result<(usize, Vec<String>), WriteError> {
328        // delete destination first
329        self.remove_if_expired(dest);
330        if let Some(old) = self.entries.remove(dest) {
331            self.memory.remove(dest, &old.value);
332            self.decrement_expiry_if_set(&old);
333            self.defer_drop(old.value);
334        }
335
336        let count = members.len();
337        if count == 0 {
338            return Ok((0, vec![]));
339        }
340
341        let member_bytes: usize = members
342            .iter()
343            .map(|m| m.len() + memory::HASHSET_MEMBER_OVERHEAD)
344            .sum();
345        self.reserve_memory(true, dest, memory::HASHSET_BASE_OVERHEAD, member_bytes)?;
346
347        let stored = members.clone();
348        let set: std::collections::HashSet<String> = members.into_iter().collect();
349        let value = Value::Set(Box::new(set));
350        self.memory.add(dest, &value);
351        let entry = Entry::new(value, None);
352        self.entries.insert(CompactString::from(dest), entry);
353        self.bump_version(dest);
354
355        Ok((count, stored))
356    }
357
358    /// Returns random members from a set without removing them.
359    ///
360    /// - `count > 0`: return up to `count` distinct members
361    /// - `count < 0`: return `|count|` members, allowing duplicates
362    /// - `count == 0`: return empty
363    pub fn srandmember(&mut self, key: &str, count: i64) -> Result<Vec<String>, WrongType> {
364        if count == 0 {
365            return Ok(vec![]);
366        }
367        let Some(entry) = self.get_live_entry(key) else {
368            return Ok(vec![]);
369        };
370        let Value::Set(ref set) = entry.value else {
371            return Err(WrongType);
372        };
373        if set.is_empty() {
374            return Ok(vec![]);
375        }
376
377        let mut rng = rand::rng();
378        let result = if count > 0 {
379            // distinct members, up to set size
380            let n = (count as usize).min(set.len());
381            set.iter()
382                .choose_multiple(&mut rng, n)
383                .into_iter()
384                .cloned()
385                .collect()
386        } else {
387            // allow duplicates, return exactly |count| elements
388            let n = count.unsigned_abs() as usize;
389            let members: Vec<&String> = set.iter().collect();
390            use rand::Rng;
391            (0..n)
392                .map(|_| members[rng.random_range(0..members.len())].clone())
393                .collect()
394        };
395
396        Ok(result)
397    }
398
399    /// Removes and returns up to `count` random members from a set.
400    pub fn spop(&mut self, key: &str, count: usize) -> Result<Vec<String>, WrongType> {
401        if self.remove_if_expired(key) || count == 0 {
402            return Ok(vec![]);
403        }
404
405        let Some(entry) = self.entries.get_mut(key) else {
406            return Ok(vec![]);
407        };
408        if !matches!(entry.value, Value::Set(_)) {
409            return Err(WrongType);
410        }
411
412        let old_entry_size = entry.entry_size(key);
413
414        // reborrow to get mutable access to the set
415        let Value::Set(ref mut set) = entry.value else {
416            unreachable!("type checked above");
417        };
418        if set.is_empty() {
419            return Ok(vec![]);
420        }
421
422        let n = count.min(set.len());
423        let mut rng = rand::rng();
424        let chosen: Vec<String> = set
425            .iter()
426            .choose_multiple(&mut rng, n)
427            .into_iter()
428            .cloned()
429            .collect();
430
431        let mut removed_bytes = 0usize;
432        for member in &chosen {
433            set.remove(member);
434            removed_bytes += member.len() + memory::HASHSET_MEMBER_OVERHEAD;
435        }
436        let is_empty = set.is_empty();
437
438        if !chosen.is_empty() {
439            self.bump_version(key);
440        }
441
442        self.cleanup_after_remove(key, old_entry_size, is_empty, removed_bytes);
443
444        Ok(chosen)
445    }
446
447    /// Checks membership for multiple members at once.
448    ///
449    /// Returns a boolean for each member in the same order.
450    pub fn smismember(&mut self, key: &str, members: &[String]) -> Result<Vec<bool>, WrongType> {
451        let Some(entry) = self.get_live_entry(key) else {
452            return Ok(vec![false; members.len()]);
453        };
454        match &entry.value {
455            Value::Set(set) => Ok(members.iter().map(|m| set.contains(m)).collect()),
456            _ => Err(WrongType),
457        }
458    }
459
460    /// Atomically moves `member` from `source` set to `destination` set.
461    ///
462    /// Returns `true` if the member was moved, `false` if it wasn't in the source
463    /// set. Returns an error if either key holds a non-set value.
464    ///
465    /// Both keys must hash to the same shard. The caller is responsible for
466    /// enforcing that constraint before routing to this method.
467    pub fn smove(
468        &mut self,
469        source: &str,
470        destination: &str,
471        member: &str,
472    ) -> Result<bool, WriteError> {
473        self.remove_if_expired(source);
474        self.remove_if_expired(destination);
475
476        // type-check source
477        match self.entries.get(source) {
478            None => return Ok(false),
479            Some(entry) => {
480                if !matches!(&entry.value, Value::Set(_)) {
481                    return Err(WriteError::WrongType);
482                }
483            }
484        }
485
486        // type-check destination if it exists
487        if let Some(entry) = self.entries.get(destination) {
488            if !matches!(&entry.value, Value::Set(_)) {
489                return Err(WriteError::WrongType);
490            }
491        }
492
493        let old_src_size = self
494            .entries
495            .get(source)
496            .map(|e| e.entry_size(source))
497            .unwrap_or(0);
498
499        // try removing the member from source
500        let removed = if let Some(entry) = self.entries.get_mut(source) {
501            if let Value::Set(ref mut set) = entry.value {
502                set.remove(member)
503            } else {
504                false
505            }
506        } else {
507            false
508        };
509
510        if !removed {
511            return Ok(false);
512        }
513
514        let member_bytes = member.len() + memory::HASHSET_MEMBER_OVERHEAD;
515
516        let src_empty = self
517            .entries
518            .get(source)
519            .map(|e| matches!(&e.value, Value::Set(s) if s.is_empty()))
520            .unwrap_or(false);
521
522        self.cleanup_after_remove(source, old_src_size, src_empty, member_bytes);
523        self.bump_version(source);
524
525        // add to destination (creates the set if needed)
526        self.sadd(destination, &[member.to_string()])?;
527
528        Ok(true)
529    }
530
531    /// Returns the cardinality of the intersection of all given sets.
532    ///
533    /// If `limit` is nonzero, the count is capped at that value.
534    /// Returns 0 if any key is missing. Returns an error if any key
535    /// holds a non-set type.
536    pub fn sintercard(&mut self, keys: &[String], limit: usize) -> Result<usize, WrongType> {
537        let members = self.sinter(keys)?;
538        let count = if limit > 0 {
539            members.len().min(limit)
540        } else {
541            members.len()
542        };
543        Ok(count)
544    }
545
546    /// Returns the cardinality (number of elements) of a set.
547    pub fn scard(&mut self, key: &str) -> Result<usize, WrongType> {
548        if self.remove_if_expired(key) {
549            return Ok(0);
550        }
551        match self.entries.get(key) {
552            None => Ok(0),
553            Some(entry) => match &entry.value {
554                Value::Set(set) => Ok(set.len()),
555                _ => Err(WrongType),
556            },
557        }
558    }
559}
560
561#[cfg(test)]
562mod tests {
563    use super::*;
564
565    #[test]
566    fn sadd_creates_set() {
567        let mut ks = Keyspace::new();
568        let added = ks.sadd("s", &["a".into(), "b".into()]).unwrap();
569        assert_eq!(added, 2);
570        assert_eq!(ks.value_type("s"), "set");
571    }
572
573    #[test]
574    fn sadd_returns_new_member_count() {
575        let mut ks = Keyspace::new();
576        ks.sadd("s", &["a".into(), "b".into()]).unwrap();
577        // add one existing, one new
578        let added = ks.sadd("s", &["b".into(), "c".into()]).unwrap();
579        assert_eq!(added, 1); // only "c" is new
580    }
581
582    #[test]
583    fn srem_removes_members() {
584        let mut ks = Keyspace::new();
585        ks.sadd("s", &["a".into(), "b".into(), "c".into()]).unwrap();
586        let removed = ks.srem("s", &["a".into(), "c".into()]).unwrap();
587        assert_eq!(removed, 2);
588        assert_eq!(ks.scard("s").unwrap(), 1);
589    }
590
591    #[test]
592    fn srem_auto_deletes_empty_set() {
593        let mut ks = Keyspace::new();
594        ks.sadd("s", &["only".into()]).unwrap();
595        ks.srem("s", &["only".into()]).unwrap();
596        assert_eq!(ks.value_type("s"), "none");
597    }
598
599    #[test]
600    fn smembers_returns_all_members() {
601        let mut ks = Keyspace::new();
602        ks.sadd("s", &["a".into(), "b".into(), "c".into()]).unwrap();
603        let mut members = ks.smembers("s").unwrap();
604        members.sort();
605        assert_eq!(members, vec!["a", "b", "c"]);
606    }
607
608    #[test]
609    fn smembers_missing_key_returns_empty() {
610        let mut ks = Keyspace::new();
611        assert_eq!(ks.smembers("missing").unwrap(), Vec::<String>::new());
612    }
613
614    #[test]
615    fn sismember_returns_true_for_existing() {
616        let mut ks = Keyspace::new();
617        ks.sadd("s", &["member".into()]).unwrap();
618        assert!(ks.sismember("s", "member").unwrap());
619    }
620
621    #[test]
622    fn sismember_returns_false_for_missing() {
623        let mut ks = Keyspace::new();
624        ks.sadd("s", &["a".into()]).unwrap();
625        assert!(!ks.sismember("s", "missing").unwrap());
626    }
627
628    #[test]
629    fn scard_returns_count() {
630        let mut ks = Keyspace::new();
631        ks.sadd("s", &["a".into(), "b".into(), "c".into()]).unwrap();
632        assert_eq!(ks.scard("s").unwrap(), 3);
633    }
634
635    #[test]
636    fn scard_missing_key_returns_zero() {
637        let mut ks = Keyspace::new();
638        assert_eq!(ks.scard("missing").unwrap(), 0);
639    }
640
641    #[test]
642    fn set_on_string_key_returns_wrongtype() {
643        let mut ks = Keyspace::new();
644        ks.set("s".into(), Bytes::from("string"), None, false, false);
645        assert!(ks.sadd("s", &["m".into()]).is_err());
646        assert!(ks.srem("s", &["m".into()]).is_err());
647        assert!(ks.smembers("s").is_err());
648        assert!(ks.sismember("s", "m").is_err());
649        assert!(ks.scard("s").is_err());
650    }
651
652    #[test]
653    fn sadd_duplicate_members_counted_once() {
654        let mut ks = Keyspace::new();
655        // add same member twice in one call
656        let count = ks.sadd("s", &["a".into(), "a".into()]).unwrap();
657        // should only count as 1 new member
658        assert_eq!(count, 1);
659        assert_eq!(ks.scard("s").unwrap(), 1);
660    }
661
662    #[test]
663    fn srem_non_existent_member_returns_zero() {
664        let mut ks = Keyspace::new();
665        ks.sadd("s", &["a".into()]).unwrap();
666        let removed = ks.srem("s", &["nonexistent".into()]).unwrap();
667        assert_eq!(removed, 0);
668    }
669
670    // --- scan_set ---
671
672    #[test]
673    fn scan_set_returns_all() {
674        let mut ks = Keyspace::new();
675        ks.sadd("s", &["a".into(), "b".into(), "c".into()]).unwrap();
676        let (cursor, members) = ks.scan_set("s", 0, 100, None).unwrap();
677        assert_eq!(cursor, 0);
678        assert_eq!(members.len(), 3);
679    }
680
681    #[test]
682    fn scan_set_missing_key() {
683        let mut ks = Keyspace::new();
684        let (cursor, members) = ks.scan_set("missing", 0, 10, None).unwrap();
685        assert_eq!(cursor, 0);
686        assert!(members.is_empty());
687    }
688
689    #[test]
690    fn scan_set_wrong_type() {
691        let mut ks = Keyspace::new();
692        ks.set("s".into(), Bytes::from("string"), None, false, false);
693        assert!(ks.scan_set("s", 0, 10, None).is_err());
694    }
695
696    #[test]
697    fn scan_set_with_pattern() {
698        let mut ks = Keyspace::new();
699        ks.sadd("s", &["user:1".into(), "user:2".into(), "item:1".into()])
700            .unwrap();
701        let (_, members) = ks.scan_set("s", 0, 100, Some("user:*")).unwrap();
702        assert_eq!(members.len(), 2);
703        assert!(members.iter().all(|m| m.starts_with("user:")));
704    }
705
706    #[test]
707    fn scan_set_pagination() {
708        let mut ks = Keyspace::new();
709        let items: Vec<String> = (0..20).map(|i| format!("m{i}")).collect();
710        ks.sadd("s", &items).unwrap();
711
712        let mut collected = Vec::new();
713        let mut cursor = 0u64;
714        loop {
715            let (next, batch) = ks.scan_set("s", cursor, 5, None).unwrap();
716            collected.extend(batch);
717            if next == 0 {
718                break;
719            }
720            cursor = next;
721        }
722        // all 20 members collected
723        assert_eq!(collected.len(), 20);
724    }
725
726    // --- sunion ---
727
728    #[test]
729    fn sunion_basic() {
730        let mut ks = Keyspace::new();
731        ks.sadd("s1", &["a".into(), "b".into()]).unwrap();
732        ks.sadd("s2", &["b".into(), "c".into()]).unwrap();
733        let mut result = ks.sunion(&["s1".into(), "s2".into()]).unwrap();
734        result.sort();
735        assert_eq!(result, vec!["a", "b", "c"]);
736    }
737
738    #[test]
739    fn sunion_with_missing_key() {
740        let mut ks = Keyspace::new();
741        ks.sadd("s1", &["a".into()]).unwrap();
742        let mut result = ks.sunion(&["s1".into(), "missing".into()]).unwrap();
743        result.sort();
744        assert_eq!(result, vec!["a"]);
745    }
746
747    #[test]
748    fn sunion_empty_keys() {
749        let mut ks = Keyspace::new();
750        assert!(ks.sunion(&[]).unwrap().is_empty());
751    }
752
753    #[test]
754    fn sunion_wrong_type() {
755        let mut ks = Keyspace::new();
756        ks.sadd("s1", &["a".into()]).unwrap();
757        ks.set("str".into(), Bytes::from("val"), None, false, false);
758        assert!(ks.sunion(&["s1".into(), "str".into()]).is_err());
759    }
760
761    // --- sinter ---
762
763    #[test]
764    fn sinter_basic() {
765        let mut ks = Keyspace::new();
766        ks.sadd("s1", &["a".into(), "b".into(), "c".into()])
767            .unwrap();
768        ks.sadd("s2", &["b".into(), "c".into(), "d".into()])
769            .unwrap();
770        let mut result = ks.sinter(&["s1".into(), "s2".into()]).unwrap();
771        result.sort();
772        assert_eq!(result, vec!["b", "c"]);
773    }
774
775    #[test]
776    fn sinter_missing_key_returns_empty() {
777        let mut ks = Keyspace::new();
778        ks.sadd("s1", &["a".into()]).unwrap();
779        let result = ks.sinter(&["s1".into(), "missing".into()]).unwrap();
780        assert!(result.is_empty());
781    }
782
783    #[test]
784    fn sinter_disjoint_sets() {
785        let mut ks = Keyspace::new();
786        ks.sadd("s1", &["a".into()]).unwrap();
787        ks.sadd("s2", &["b".into()]).unwrap();
788        let result = ks.sinter(&["s1".into(), "s2".into()]).unwrap();
789        assert!(result.is_empty());
790    }
791
792    #[test]
793    fn sinter_wrong_type() {
794        let mut ks = Keyspace::new();
795        ks.set("str".into(), Bytes::from("val"), None, false, false);
796        assert!(ks.sinter(&["str".into()]).is_err());
797    }
798
799    // --- sdiff ---
800
801    #[test]
802    fn sdiff_basic() {
803        let mut ks = Keyspace::new();
804        ks.sadd("s1", &["a".into(), "b".into(), "c".into()])
805            .unwrap();
806        ks.sadd("s2", &["b".into(), "d".into()]).unwrap();
807        let mut result = ks.sdiff(&["s1".into(), "s2".into()]).unwrap();
808        result.sort();
809        assert_eq!(result, vec!["a", "c"]);
810    }
811
812    #[test]
813    fn sdiff_missing_first_key() {
814        let mut ks = Keyspace::new();
815        ks.sadd("s2", &["a".into()]).unwrap();
816        let result = ks.sdiff(&["missing".into(), "s2".into()]).unwrap();
817        assert!(result.is_empty());
818    }
819
820    #[test]
821    fn sdiff_missing_second_key() {
822        let mut ks = Keyspace::new();
823        ks.sadd("s1", &["a".into(), "b".into()]).unwrap();
824        let mut result = ks.sdiff(&["s1".into(), "missing".into()]).unwrap();
825        result.sort();
826        assert_eq!(result, vec!["a", "b"]);
827    }
828
829    // --- sunionstore / sinterstore / sdiffstore ---
830
831    #[test]
832    fn sunionstore_basic() {
833        let mut ks = Keyspace::new();
834        ks.sadd("s1", &["a".into(), "b".into()]).unwrap();
835        ks.sadd("s2", &["b".into(), "c".into()]).unwrap();
836        let (count, _) = ks.sunionstore("dest", &["s1".into(), "s2".into()]).unwrap();
837        assert_eq!(count, 3);
838        let mut members = ks.smembers("dest").unwrap();
839        members.sort();
840        assert_eq!(members, vec!["a", "b", "c"]);
841    }
842
843    #[test]
844    fn sinterstore_basic() {
845        let mut ks = Keyspace::new();
846        ks.sadd("s1", &["a".into(), "b".into(), "c".into()])
847            .unwrap();
848        ks.sadd("s2", &["b".into(), "c".into(), "d".into()])
849            .unwrap();
850        let (count, _) = ks.sinterstore("dest", &["s1".into(), "s2".into()]).unwrap();
851        assert_eq!(count, 2);
852        let mut members = ks.smembers("dest").unwrap();
853        members.sort();
854        assert_eq!(members, vec!["b", "c"]);
855    }
856
857    #[test]
858    fn sdiffstore_basic() {
859        let mut ks = Keyspace::new();
860        ks.sadd("s1", &["a".into(), "b".into(), "c".into()])
861            .unwrap();
862        ks.sadd("s2", &["b".into()]).unwrap();
863        let (count, _) = ks.sdiffstore("dest", &["s1".into(), "s2".into()]).unwrap();
864        assert_eq!(count, 2);
865    }
866
867    #[test]
868    fn store_overwrites_destination() {
869        let mut ks = Keyspace::new();
870        ks.sadd("dest", &["old".into()]).unwrap();
871        ks.sadd("s1", &["new".into()]).unwrap();
872        ks.sunionstore("dest", &["s1".into()]).unwrap();
873        let members = ks.smembers("dest").unwrap();
874        assert_eq!(members, vec!["new"]);
875    }
876
877    #[test]
878    fn store_empty_result_deletes_dest() {
879        let mut ks = Keyspace::new();
880        ks.sadd("dest", &["old".into()]).unwrap();
881        // intersect with missing key → empty result
882        ks.sinterstore("dest", &["missing".into()]).unwrap();
883        assert_eq!(ks.value_type("dest"), "none");
884    }
885
886    // --- srandmember ---
887
888    #[test]
889    fn srandmember_positive_count() {
890        let mut ks = Keyspace::new();
891        ks.sadd("s", &["a".into(), "b".into(), "c".into()]).unwrap();
892        let result = ks.srandmember("s", 2).unwrap();
893        assert_eq!(result.len(), 2);
894        // all returned members should be from the set
895        for m in &result {
896            assert!(["a", "b", "c"].contains(&m.as_str()));
897        }
898        // results should be distinct
899        let unique: std::collections::HashSet<_> = result.iter().collect();
900        assert_eq!(unique.len(), 2);
901    }
902
903    #[test]
904    fn srandmember_count_larger_than_set() {
905        let mut ks = Keyspace::new();
906        ks.sadd("s", &["a".into(), "b".into()]).unwrap();
907        let result = ks.srandmember("s", 10).unwrap();
908        assert_eq!(result.len(), 2); // capped at set size
909    }
910
911    #[test]
912    fn srandmember_negative_count_allows_duplicates() {
913        let mut ks = Keyspace::new();
914        ks.sadd("s", &["only".into()]).unwrap();
915        let result = ks.srandmember("s", -5).unwrap();
916        assert_eq!(result.len(), 5);
917        assert!(result.iter().all(|m| m == "only"));
918    }
919
920    #[test]
921    fn srandmember_zero_returns_empty() {
922        let mut ks = Keyspace::new();
923        ks.sadd("s", &["a".into()]).unwrap();
924        assert!(ks.srandmember("s", 0).unwrap().is_empty());
925    }
926
927    #[test]
928    fn srandmember_missing_key() {
929        let mut ks = Keyspace::new();
930        assert!(ks.srandmember("missing", 1).unwrap().is_empty());
931    }
932
933    #[test]
934    fn srandmember_wrong_type() {
935        let mut ks = Keyspace::new();
936        ks.set("str".into(), Bytes::from("val"), None, false, false);
937        assert!(ks.srandmember("str", 1).is_err());
938    }
939
940    // --- spop ---
941
942    #[test]
943    fn spop_basic() {
944        let mut ks = Keyspace::new();
945        ks.sadd("s", &["a".into(), "b".into(), "c".into()]).unwrap();
946        let result = ks.spop("s", 1).unwrap();
947        assert_eq!(result.len(), 1);
948        assert_eq!(ks.scard("s").unwrap(), 2);
949    }
950
951    #[test]
952    fn spop_all_members() {
953        let mut ks = Keyspace::new();
954        ks.sadd("s", &["a".into(), "b".into()]).unwrap();
955        let result = ks.spop("s", 10).unwrap();
956        assert_eq!(result.len(), 2);
957        assert_eq!(ks.value_type("s"), "none"); // auto-deleted
958    }
959
960    #[test]
961    fn spop_missing_key() {
962        let mut ks = Keyspace::new();
963        assert!(ks.spop("missing", 1).unwrap().is_empty());
964    }
965
966    #[test]
967    fn spop_wrong_type() {
968        let mut ks = Keyspace::new();
969        ks.set("str".into(), Bytes::from("val"), None, false, false);
970        assert!(ks.spop("str", 1).is_err());
971    }
972
973    #[test]
974    fn spop_zero_count() {
975        let mut ks = Keyspace::new();
976        ks.sadd("s", &["a".into()]).unwrap();
977        assert!(ks.spop("s", 0).unwrap().is_empty());
978        assert_eq!(ks.scard("s").unwrap(), 1);
979    }
980
981    // --- smismember ---
982
983    #[test]
984    fn smismember_basic() {
985        let mut ks = Keyspace::new();
986        ks.sadd("s", &["a".into(), "c".into()]).unwrap();
987        let result = ks
988            .smismember("s", &["a".into(), "b".into(), "c".into()])
989            .unwrap();
990        assert_eq!(result, vec![true, false, true]);
991    }
992
993    #[test]
994    fn smismember_missing_key() {
995        let mut ks = Keyspace::new();
996        let result = ks.smismember("missing", &["a".into()]).unwrap();
997        assert_eq!(result, vec![false]);
998    }
999
1000    #[test]
1001    fn smismember_wrong_type() {
1002        let mut ks = Keyspace::new();
1003        ks.set("str".into(), Bytes::from("val"), None, false, false);
1004        assert!(ks.smismember("str", &["a".into()]).is_err());
1005    }
1006
1007    #[test]
1008    fn set_auto_deleted_when_empty() {
1009        let mut ks = Keyspace::new();
1010        ks.sadd("s", &["a".into(), "b".into()]).unwrap();
1011        assert_eq!(ks.len(), 1);
1012
1013        // remove all members
1014        ks.srem("s", &["a".into(), "b".into()]).unwrap();
1015
1016        // set should be auto-deleted
1017        assert_eq!(ks.len(), 0);
1018        assert!(!ks.exists("s"));
1019    }
1020
1021    // --- smove ---
1022
1023    #[test]
1024    fn smove_moves_member() {
1025        let mut ks = Keyspace::new();
1026        ks.sadd("src", &["a".into(), "b".into()]).unwrap();
1027
1028        let moved = ks.smove("src", "dst", "a").unwrap();
1029        assert!(moved);
1030        assert!(!ks.sismember("src", "a").unwrap());
1031        assert!(ks.sismember("dst", "a").unwrap());
1032        assert_eq!(ks.scard("src").unwrap(), 1);
1033        assert_eq!(ks.scard("dst").unwrap(), 1);
1034    }
1035
1036    #[test]
1037    fn smove_missing_member_returns_false() {
1038        let mut ks = Keyspace::new();
1039        ks.sadd("src", &["x".into()]).unwrap();
1040
1041        let moved = ks.smove("src", "dst", "missing").unwrap();
1042        assert!(!moved);
1043        assert_eq!(ks.scard("src").unwrap(), 1);
1044        assert!(!ks.exists("dst"));
1045    }
1046
1047    #[test]
1048    fn smove_missing_source_returns_false() {
1049        let mut ks = Keyspace::new();
1050        let moved = ks.smove("nosrc", "dst", "m").unwrap();
1051        assert!(!moved);
1052    }
1053
1054    #[test]
1055    fn smove_removes_empty_source() {
1056        let mut ks = Keyspace::new();
1057        ks.sadd("src", &["only".into()]).unwrap();
1058
1059        ks.smove("src", "dst", "only").unwrap();
1060        // source set is auto-deleted when it becomes empty
1061        assert!(!ks.exists("src"));
1062        assert_eq!(ks.scard("dst").unwrap(), 1);
1063    }
1064
1065    #[test]
1066    fn smove_wrong_type_source_returns_error() {
1067        let mut ks = Keyspace::new();
1068        ks.set("src".into(), Bytes::from("string"), None, false, false);
1069        assert!(ks.smove("src", "dst", "m").is_err());
1070    }
1071
1072    #[test]
1073    fn smove_wrong_type_destination_returns_error() {
1074        let mut ks = Keyspace::new();
1075        ks.sadd("src", &["m".into()]).unwrap();
1076        ks.set("dst".into(), Bytes::from("string"), None, false, false);
1077        assert!(ks.smove("src", "dst", "m").is_err());
1078    }
1079
1080    // --- sintercard ---
1081
1082    #[test]
1083    fn sintercard_basic() {
1084        let mut ks = Keyspace::new();
1085        ks.sadd("s1", &["a".into(), "b".into(), "c".into()])
1086            .unwrap();
1087        ks.sadd("s2", &["b".into(), "c".into(), "d".into()])
1088            .unwrap();
1089
1090        assert_eq!(ks.sintercard(&["s1".into(), "s2".into()], 0).unwrap(), 2);
1091    }
1092
1093    #[test]
1094    fn sintercard_with_limit() {
1095        let mut ks = Keyspace::new();
1096        ks.sadd("s1", &["a".into(), "b".into(), "c".into()])
1097            .unwrap();
1098        ks.sadd("s2", &["a".into(), "b".into(), "c".into()])
1099            .unwrap();
1100
1101        // limit caps the result
1102        assert_eq!(ks.sintercard(&["s1".into(), "s2".into()], 2).unwrap(), 2);
1103        // limit 0 means no cap
1104        assert_eq!(ks.sintercard(&["s1".into(), "s2".into()], 0).unwrap(), 3);
1105    }
1106
1107    #[test]
1108    fn sintercard_missing_key_returns_zero() {
1109        let mut ks = Keyspace::new();
1110        ks.sadd("s1", &["a".into()]).unwrap();
1111
1112        assert_eq!(
1113            ks.sintercard(&["s1".into(), "missing".into()], 0).unwrap(),
1114            0
1115        );
1116    }
1117
1118    #[test]
1119    fn sintercard_wrong_type_returns_error() {
1120        let mut ks = Keyspace::new();
1121        ks.set("str".into(), Bytes::from("val"), None, false, false);
1122        assert!(ks.sintercard(&["str".into()], 0).is_err());
1123    }
1124}