Skip to main content

yo_kv/
keys.rs

1//! Moving a key, copying one, and touching one.
2//!
3//! Three of the four commands here move whole values around, and all three of
4//! them are careful about the same thing: a value lives in two places at once.
5//! A string lives entirely in its record, and a set or a hash lives in a slab
6//! with the record holding nothing but a slot number. So there is no one way to
7//! move a value, and a command that forgets which case it is in either drops
8//! members on the floor or leaves a body in the slab that nothing points at.
9//!
10//! [`Keyspace::rename`] moves the record's bytes and leaves the body exactly
11//! where it is, because a slot number that moves to a different key is still
12//! the same slot. Renaming a set of a million members writes thirteen bytes.
13//!
14//! [`Keyspace::copy`] cannot do that, since two records pointing at one slot
15//! would be one set that answers to two names and `SADD` to either would show
16//! up in both. So the body is cloned, which is the one thing here that costs
17//! what the value is worth. That is Redis's cost too and there is no version of
18//! `COPY` that avoids it.
19//!
20//! # Why export and import are separate and public
21//!
22//! `COPY key dst DB n` puts a value in a database this one cannot reach. The
23//! wire layer holds every database and this one holds none of them, so the two
24//! halves are separate calls and the caller is what joins them up.
25//!
26//! It also makes the pair the answer for `MOVE`, `DUMP` and `RESTORE` when they
27//! land, which want exactly this: a value lifted out of a database, standing on
28//! its own with its deadline attached.
29
30use yo_common::Result;
31
32use crate::array::Array;
33use crate::hash::Hash;
34use crate::keyspace::Keyspace;
35use crate::list::List;
36use crate::set::Set;
37use crate::value::{self, Kind};
38use crate::zset::Zset;
39
40/// Everything under one key, lifted out so it can be put somewhere else.
41///
42/// It owns what it holds. A record taken out of a database survives that
43/// database being written to, flushed or dropped, which is what makes it safe
44/// to carry between two of them.
45#[derive(Debug, Clone)]
46pub struct Record {
47    body: Body,
48    /// The deadline, which travels with the value. `COPY` and `RENAME` both
49    /// keep it, and a copy of a key with ten seconds left has ten seconds left.
50    expire_at: Option<u64>,
51}
52
53impl Record {
54    /// What type this is, which the caller usually knows and sometimes does not.
55    #[must_use]
56    pub const fn kind(&self) -> Kind {
57        match self.body {
58            Body::String(_) => Kind::String,
59            Body::Set(_) => Kind::Set,
60            Body::Hash(_) => Kind::Hash,
61            Body::List(_) => Kind::List,
62            Body::Zset(_) => Kind::Zset,
63            Body::Array(_) => Kind::Array,
64        }
65    }
66
67    /// When it goes away, if anything says.
68    #[must_use]
69    pub const fn expire_at(&self) -> Option<u64> {
70        self.expire_at
71    }
72}
73
74/// The six things a record can be, owned rather than borrowed.
75///
76/// One variant per type that a key can hold, and that is the point: the day a
77/// sixth type lands, the compiler names this file. It did not before, because
78/// the match in [`Keyspace::export`] had a catch all arm at the bottom, and a
79/// catch all in front of an enum the rest of the crate keeps growing is a hole
80/// that reports itself as a panic on a live server rather than as a build error.
81#[derive(Debug, Clone)]
82enum Body {
83    String(Vec<u8>),
84    Set(Set),
85    Hash(Hash),
86    List(List),
87    Zset(Zset),
88    Array(Array),
89}
90
91/// What a rename or a copy did.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum Moved {
94    /// There was no source key, so there was nothing to move.
95    Missing,
96    /// The destination was there and the caller said not to write over it.
97    Taken,
98    /// It happened.
99    Ok,
100}
101
102impl Keyspace {
103    /// Take a copy of everything under `key`, deadline included.
104    ///
105    /// `None` for a key that is not there, and for one whose deadline has gone,
106    /// which is reaped on the way through the same as every other read.
107    ///
108    /// This clones the body, so exporting a set of a million members costs a set
109    /// of a million members. [`Keyspace::rename`] exists so that the one case
110    /// which does not need a copy does not pay for one.
111    pub fn export(&mut self, key: &[u8]) -> Option<Record> {
112        let addr = self.live_rec(key)?;
113        let rec = self.map.value_at(addr);
114        let expire_at = value::expire_at(rec);
115        // The slot is read inside the arms and not before them. A string record
116        // holds the string and not a slot, so reading four bytes where the slot
117        // would be reads off the end of a short one.
118        let body = match value::kind(rec) {
119            Kind::String => Body::String(value::read(rec).to_vec()),
120            Kind::Set => Body::Set(
121                self.sets
122                    .get(value::slot(rec))
123                    .expect("the record points at its body")
124                    .clone(),
125            ),
126            Kind::Hash => Body::Hash(
127                self.hashes
128                    .get(value::slot(rec))
129                    .expect("the record points at its body")
130                    .clone(),
131            ),
132            Kind::List => Body::List(
133                self.lists
134                    .get(value::slot(rec))
135                    .expect("the record points at its body")
136                    .clone(),
137            ),
138            Kind::Zset => Body::Zset(
139                self.zsets
140                    .get(value::slot(rec))
141                    .expect("the record points at its body")
142                    .clone(),
143            ),
144            Kind::Array => Body::Array(
145                self.arrays
146                    .get(value::slot(rec))
147                    .expect("the record points at its body")
148                    .clone(),
149            ),
150            // A stream is the one type a key can hold that nothing can put
151            // there yet, so this arm is the only one left and it names it.
152            Kind::Stream => unreachable!("nothing can store a stream yet"),
153        };
154        Some(Record { body, expire_at })
155    }
156
157    /// Put `rec` under `key`, over whatever was there.
158    ///
159    /// The caller has already decided that writing over the destination is
160    /// allowed, which is why this answers nothing. Whatever was under `key` is
161    /// freed first, body and all, so this cannot leak a slab slot.
162    pub fn import(&mut self, key: &[u8], rec: Record) {
163        let at = rec.expire_at;
164        match rec.body {
165            // The string path frees the old body itself, because every string
166            // write has to and this is not the place to make it special.
167            Body::String(bytes) => self.store(key, &bytes, at),
168            Body::Set(set) => {
169                self.free_body(key);
170                let slot = self.sets.insert(set);
171                self.bodies += 1;
172                self.write_slot(key, Kind::Set, slot, at);
173            }
174            Body::Hash(hash) => {
175                self.free_body(key);
176                let slot = self.hashes.insert(hash);
177                self.bodies += 1;
178                self.write_slot(key, Kind::Hash, slot, at);
179            }
180            Body::List(list) => {
181                self.free_body(key);
182                let slot = self.lists.insert(list);
183                self.bodies += 1;
184                self.write_slot(key, Kind::List, slot, at);
185            }
186            Body::Zset(zset) => {
187                self.free_body(key);
188                let slot = self.zsets.insert(zset);
189                self.bodies += 1;
190                self.write_slot(key, Kind::Zset, slot, at);
191            }
192            Body::Array(array) => {
193                self.free_body(key);
194                let slot = self.arrays.insert(array);
195                self.bodies += 1;
196                self.write_slot(key, Kind::Array, slot, at);
197            }
198        }
199    }
200
201    /// `RENAME src dst`, and `RENAMENX` when `only_if_new`.
202    ///
203    /// The body never moves. A set or a hash is a slot number in a record, and a
204    /// slot number under a different key is the same set, so this writes the
205    /// source's record bytes under the destination and deletes the source
206    /// record without freeing anything. That is why renaming a large collection
207    /// is the same call as renaming a short string.
208    ///
209    /// The deadline travels with the source and the destination's own deadline
210    /// goes with the value it belonged to, which falls out of moving the whole
211    /// record rather than being a rule applied on top of it.
212    ///
213    /// Renaming a key onto itself is allowed and does nothing, which is Redis's
214    /// answer. `RENAMENX` on the same key answers [`Moved::Taken`] instead,
215    /// because the destination does exist, and a key is not new because it is
216    /// the one you already had.
217    pub fn rename(&mut self, src: &[u8], dst: &[u8], only_if_new: bool) -> Moved {
218        if self.live_rec(src).is_none() {
219            return Moved::Missing;
220        }
221        let same = src == dst;
222        if only_if_new && (same || self.live_rec(dst).is_some()) {
223            return Moved::Taken;
224        }
225        if same {
226            return Moved::Ok;
227        }
228        // The record and not the value: a tag, a deadline and then either the
229        // string itself or four bytes saying which slot the body is in. Copying
230        // it out ends the borrow of the map so the write below can begin.
231        //
232        // Into the database's scratch buffer rather than a fresh `Vec`, because
233        // a record under a collection key is nine bytes and `RENAME` is not
234        // rare enough to pay a malloc and a free for nine bytes. Taken out and
235        // put back, so the map is free to be borrowed in between.
236        let addr = self.map.find(src).expect("it was live a line ago");
237        let mut bytes = std::mem::take(&mut self.scratch);
238        bytes.clear();
239        bytes.extend_from_slice(self.map.value_at(addr));
240        self.free_body(dst);
241        self.write_rec(dst, bytes.len(), |out| {
242            out.copy_from_slice(&bytes);
243        });
244        self.scratch = bytes;
245        // `del_rec` and not `drop_key`, which is the whole point. The body under
246        // the source belongs to the destination now and freeing it here would
247        // take it away from the key that just gained it. It still goes through
248        // `del_rec` rather than straight at the map, because the record is going
249        // away either way and the count of keys with deadlines has to hear about
250        // it.
251        self.del_rec(src);
252        Moved::Ok
253    }
254
255    /// `COPY src dst`, within one database.
256    ///
257    /// Across two databases the caller runs [`Keyspace::export`] on one and
258    /// [`Keyspace::import`] on the other, because a database cannot see its
259    /// neighbours from in here.
260    ///
261    /// A destination whose deadline has gone counts as free, so this answers
262    /// [`Moved::Ok`] without `replace` on a key that has technically expired and
263    /// not yet been collected. That is Redis's behaviour and it is the only one
264    /// that is consistent with `EXISTS` saying zero for the same key.
265    /// A key copied onto itself answers [`Moved::Ok`] and does nothing, and
266    /// without `replace` it answers [`Moved::Taken`], which is the same pair of
267    /// answers [`Keyspace::rename`] gives. The wire never asks: Redis refuses
268    /// `COPY k k` with an error and so does the dispatch. This is for the
269    /// embedded caller, who can ask, and for whom freeing the body and then
270    /// writing a record that points at it would be the worst of the answers
271    /// available.
272    pub fn copy(&mut self, src: &[u8], dst: &[u8], replace: bool) -> Moved {
273        if self.live_rec(src).is_none() {
274            return Moved::Missing;
275        }
276        let same = src == dst;
277        if !replace && (same || self.live_rec(dst).is_some()) {
278            return Moved::Taken;
279        }
280        if same {
281            return Moved::Ok;
282        }
283        // The destination is settled before anything is copied, which is the
284        // difference between a refused copy of a million member set costing
285        // nothing and costing the set.
286        //
287        // Both keys have been reaped by now, so the address below stays good
288        // for as long as it is held. It is read after the reaping and not
289        // before, because a reap can move records around.
290        let addr = self.map.find(src).expect("it was live a line ago");
291        if value::kind(self.map.value_at(addr)) == Kind::String {
292            // A string record is the value, deadline and all, so copying the
293            // record is copying the key. That is [`Keyspace::rename`]'s trick,
294            // except the source stays where it is, and it goes through the
295            // database's scratch buffer for the same reason: the borrow of the
296            // map has to end before the write can begin, and a short string is
297            // not worth a malloc and a free.
298            let mut bytes = std::mem::take(&mut self.scratch);
299            bytes.clear();
300            bytes.extend_from_slice(self.map.value_at(addr));
301            self.free_body(dst);
302            self.write_rec(dst, bytes.len(), |out| {
303                out.copy_from_slice(&bytes);
304            });
305            self.scratch = bytes;
306            return Moved::Ok;
307        }
308        // A collection is a clone and there is no way around that: the
309        // destination has to end up owning a set of its own.
310        let rec = self.export(src).expect("it was live a line ago");
311        self.import(dst, rec);
312        Moved::Ok
313    }
314
315    /// `TOUCH key [key ...]`. Answers how many of them are there.
316    ///
317    /// The same answer `EXISTS` gives, including a key named twice counting
318    /// twice. On a real server the difference is that this moves the key up the
319    /// eviction order, and there is no eviction here yet, so for now the two are
320    /// the same walk and the day eviction lands this is where the bump goes.
321    pub fn touch<'k>(&mut self, keys: impl Iterator<Item = &'k [u8]>) -> usize {
322        keys.filter(|key| self.exists(key)).count()
323    }
324
325    /// The record a set or a hash gets: a tag, a slot number and maybe a
326    /// deadline. Both arms of [`Keyspace::import`] want it and neither wants to
327    /// spell it out.
328    fn write_slot(&mut self, key: &[u8], kind: Kind, slot: u32, at: Option<u64>) {
329        let len = value::slot_record_len(at.is_some());
330        self.write_rec(key, len, |out| {
331            value::write_slot_record(out, kind, slot, at);
332        });
333    }
334}
335
336/// The error `RENAME` and `RENAMENX` answer for a source that is not there.
337///
338/// It is the same sentence for both and it is an error and not a zero, which is
339/// unusual enough among the keyspace commands to be worth its own name: every
340/// other command here treats a missing key as an ordinary answer.
341#[must_use]
342pub fn no_such_key() -> yo_common::Error {
343    yo_common::Error::new(yo_common::Code::Invalid, "no such key")
344}
345
346/// So that a caller can write `?` on a rename without unpacking the enum.
347///
348/// [`Moved::Taken`] is not an error here, because for `RENAMENX` it is the whole
349/// answer and for `RENAME` it cannot happen.
350impl Moved {
351    /// The source was there, or the error `RENAME` gives when it was not.
352    ///
353    /// # Errors
354    ///
355    /// [`yo_common::Code::Invalid`] with Redis's `no such key` for
356    /// [`Moved::Missing`].
357    pub fn found(self) -> Result<Moved> {
358        match self {
359            Moved::Missing => Err(no_such_key()),
360            other => Ok(other),
361        }
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368    use crate::Clock;
369    use crate::End;
370    use crate::zsets::ZAdd;
371
372    fn db() -> Keyspace {
373        Keyspace::with_clock(Clock::fixed(1_000_000))
374    }
375
376    fn members(d: &mut Keyspace, key: &[u8]) -> Vec<String> {
377        let mut out: Vec<String> = d
378            .smembers(key)
379            .expect("a set")
380            .expect("a key")
381            .map(|m| String::from_utf8(m.to_vec()).expect("utf8 in these tests"))
382            .collect();
383        out.sort();
384        out
385    }
386
387    fn put(d: &mut Keyspace, key: &[u8], val: &[u8]) {
388        d.set_plain(key, val).expect("room for a record");
389    }
390
391    fn read(d: &mut Keyspace, key: &[u8]) -> Vec<u8> {
392        d.get(key).expect("a string").expect("there").to_vec()
393    }
394
395    #[test]
396    fn a_rename_moves_the_value_and_leaves_nothing_behind() {
397        let mut d = db();
398        put(&mut d, b"a", b"v1");
399
400        assert_eq!(d.rename(b"a", b"b", false), Moved::Ok);
401        assert!(!d.exists(b"a"));
402        assert_eq!(read(&mut d, b"b"), b"v1");
403    }
404
405    /// `RENAME` used to copy the source record into a fresh `Vec` so it could
406    /// let go of the map before writing, and that record is nine bytes when the
407    /// key holds a collection.
408    #[test]
409    fn a_rename_does_not_allocate_to_carry_the_record_across() {
410        let mut d = db();
411        put(&mut d, b"a", b"v1");
412        // Both names get used before the count starts, so the map has already
413        // made room for them and the loop below is renames and nothing else.
414        for _ in 0..4 {
415            assert_eq!(d.rename(b"a", b"b", false), Moved::Ok);
416            assert_eq!(d.rename(b"b", b"a", false), Moved::Ok);
417        }
418        let (_, allocs) = crate::tally::counted(|| {
419            for _ in 0..50 {
420                assert_eq!(d.rename(b"a", b"b", false), Moved::Ok);
421                assert_eq!(d.rename(b"b", b"a", false), Moved::Ok);
422            }
423        });
424        assert_eq!(allocs, 0, "rename allocated {allocs} times in a hundred");
425        assert_eq!(read(&mut d, b"a"), b"v1");
426    }
427
428    #[test]
429    fn a_rename_with_no_source_is_the_one_error_in_this_file() {
430        let mut d = db();
431        assert_eq!(d.rename(b"a", b"b", false), Moved::Missing);
432        assert_eq!(d.rename(b"a", b"b", true), Moved::Missing);
433        assert_eq!(
434            d.copy(b"a", b"b", false),
435            Moved::Missing,
436            "copy just says 0"
437        );
438    }
439
440    #[test]
441    fn a_rename_carries_the_source_deadline_and_drops_the_destination_one() {
442        let mut d = db();
443        put(&mut d, b"a", b"v1");
444        d.set_expiry(b"a", Some(2_000_000));
445        put(&mut d, b"b", b"v2");
446        d.set_expiry(b"b", Some(1_500_000));
447
448        assert_eq!(d.rename(b"a", b"b", false), Moved::Ok);
449        assert_eq!(d.deadline_of(b"b"), crate::Ask::At(2_000_000));
450    }
451
452    #[test]
453    fn renaming_a_key_onto_itself_keeps_it_and_renamenx_refuses() {
454        let mut d = db();
455        put(&mut d, b"a", b"v1");
456        d.set_expiry(b"a", Some(2_000_000));
457
458        assert_eq!(d.rename(b"a", b"a", false), Moved::Ok);
459        assert_eq!(read(&mut d, b"a"), b"v1");
460        assert_eq!(d.deadline_of(b"a"), crate::Ask::At(2_000_000));
461        assert_eq!(d.rename(b"a", b"a", true), Moved::Taken);
462    }
463
464    #[test]
465    fn renamenx_writes_over_nothing() {
466        let mut d = db();
467        put(&mut d, b"a", b"v1");
468        put(&mut d, b"b", b"v2");
469
470        assert_eq!(d.rename(b"a", b"b", true), Moved::Taken);
471        assert_eq!(read(&mut d, b"a"), b"v1");
472        assert_eq!(read(&mut d, b"b"), b"v2");
473        assert_eq!(d.rename(b"a", b"c", true), Moved::Ok);
474        assert!(!d.exists(b"a"));
475    }
476
477    #[test]
478    fn renaming_a_set_moves_the_slot_and_not_the_members() {
479        let mut d = db();
480        d.sadd(b"s", [b"m1".as_ref(), b"m2".as_ref()].into_iter())
481            .expect("a set");
482        let before = d.memory_bytes();
483
484        assert_eq!(d.rename(b"s", b"t", false), Moved::Ok);
485        assert_eq!(members(&mut d, b"t"), ["m1", "m2"]);
486        assert_eq!(d.kind_of(b"t"), Some(Kind::Set));
487        assert!(!d.exists(b"s"));
488        // The record moved and the body did not, so the only thing that can
489        // have changed size is the record itself.
490        assert!(
491            d.memory_bytes().abs_diff(before) < 64,
492            "the members were not copied"
493        );
494    }
495
496    #[test]
497    fn renaming_over_a_set_frees_the_set_that_was_there() {
498        let mut d = db();
499        d.sadd(b"s", [b"m1".as_ref()].into_iter()).expect("a set");
500        d.sadd(b"t", [b"m2".as_ref()].into_iter()).expect("a set");
501        assert_eq!(d.sets.len(), 2);
502
503        assert_eq!(d.rename(b"s", b"t", false), Moved::Ok);
504        assert_eq!(d.sets.len(), 1, "the destination's body went with it");
505        assert_eq!(members(&mut d, b"t"), ["m1"]);
506    }
507
508    #[test]
509    fn a_copy_is_a_second_value_and_not_a_second_name() {
510        let mut d = db();
511        d.sadd(b"s", [b"m1".as_ref(), b"m2".as_ref()].into_iter())
512            .expect("a set");
513
514        assert_eq!(d.copy(b"s", b"t", false), Moved::Ok);
515        d.sadd(b"t", [b"m3".as_ref()].into_iter()).expect("a set");
516        assert_eq!(
517            members(&mut d, b"s"),
518            ["m1", "m2"],
519            "the original is intact"
520        );
521        assert_eq!(members(&mut d, b"t"), ["m1", "m2", "m3"]);
522    }
523
524    #[test]
525    fn a_copy_refuses_a_destination_it_was_not_told_it_could_have() {
526        let mut d = db();
527        put(&mut d, b"a", b"v1");
528        put(&mut d, b"b", b"v2");
529
530        assert_eq!(d.copy(b"a", b"b", false), Moved::Taken);
531        assert_eq!(read(&mut d, b"b"), b"v2");
532        assert_eq!(d.copy(b"a", b"b", true), Moved::Ok);
533        assert_eq!(read(&mut d, b"b"), b"v1");
534    }
535
536    /// `COPY` of a string used to go through `export`, which builds a `Vec` of
537    /// the value so that `import` can copy it into the map and drop it.
538    #[test]
539    fn a_copy_of_a_string_does_not_allocate() {
540        let mut d = db();
541        put(&mut d, b"a", b"a-value-of-some-length");
542        // Warmed up, so the map has already made room for both names and the
543        // loop below is copies and nothing else.
544        for _ in 0..4 {
545            assert_eq!(d.copy(b"a", b"b", true), Moved::Ok);
546        }
547        let (_, allocs) = crate::tally::counted(|| {
548            for _ in 0..50 {
549                assert_eq!(d.copy(b"a", b"b", true), Moved::Ok);
550            }
551        });
552        assert_eq!(allocs, 0, "copy allocated {allocs} times in fifty");
553        assert_eq!(read(&mut d, b"b"), b"a-value-of-some-length");
554    }
555
556    /// The embedded caller can ask for this and the wire cannot, because the
557    /// dispatch turns it into an error before it gets here. Freeing the body
558    /// and then writing a record that still points at it would be the way to
559    /// get this wrong.
560    #[test]
561    fn a_copy_onto_itself_leaves_the_key_alone() {
562        let mut d = db();
563        d.sadd(b"s", [b"m1".as_ref(), b"m2".as_ref()].into_iter())
564            .expect("a set");
565
566        assert_eq!(d.copy(b"s", b"s", false), Moved::Taken);
567        assert_eq!(d.copy(b"s", b"s", true), Moved::Ok);
568        assert_eq!(members(&mut d, b"s"), ["m1", "m2"]);
569        assert_eq!(d.sets.len(), 1, "no second body was made or lost");
570    }
571
572    #[test]
573    fn a_copy_carries_the_deadline() {
574        let mut d = db();
575        put(&mut d, b"a", b"v1");
576        d.set_expiry(b"a", Some(2_000_000));
577
578        assert_eq!(d.copy(b"a", b"b", false), Moved::Ok);
579        assert_eq!(d.deadline_of(b"b"), crate::Ask::At(2_000_000));
580        assert_eq!(d.deadline_of(b"a"), crate::Ask::At(2_000_000));
581    }
582
583    #[test]
584    fn a_destination_that_has_already_gone_counts_as_free() {
585        let mut d = db();
586        put(&mut d, b"a", b"v1");
587        put(&mut d, b"b", b"v2");
588        d.set_expiry(b"b", Some(999_999));
589
590        assert_eq!(d.copy(b"a", b"b", false), Moved::Ok, "b was already gone");
591        assert_eq!(read(&mut d, b"b"), b"v1");
592    }
593
594    #[test]
595    fn a_source_that_has_already_gone_is_not_a_source() {
596        let mut d = db();
597        put(&mut d, b"a", b"v1");
598        d.set_expiry(b"a", Some(999_999));
599
600        assert_eq!(d.rename(b"a", b"b", false), Moved::Missing);
601        assert_eq!(d.copy(b"a", b"b", false), Moved::Missing);
602    }
603
604    #[test]
605    fn a_record_taken_out_of_a_database_outlives_it() {
606        let mut from = db();
607        from.sadd(b"s", [b"m1".as_ref(), b"m2".as_ref()].into_iter())
608            .expect("a set");
609        let rec = from.export(b"s").expect("a record");
610        assert_eq!(rec.kind(), Kind::Set);
611        from.clear();
612
613        let mut into = db();
614        into.import(b"s", rec);
615        assert_eq!(members(&mut into, b"s"), ["m1", "m2"]);
616    }
617
618    #[test]
619    fn importing_over_a_body_does_not_leave_it_in_the_slab() {
620        let mut d = db();
621        d.sadd(b"s", [b"m1".as_ref()].into_iter()).expect("a set");
622        d.sadd(b"t", [b"m2".as_ref()].into_iter()).expect("a set");
623        let rec = d.export(b"s").expect("a record");
624
625        d.import(b"t", rec);
626        assert_eq!(d.sets.len(), 2, "s and t, and not the one t used to hold");
627        assert_eq!(members(&mut d, b"t"), ["m1"]);
628    }
629
630    #[test]
631    fn importing_a_string_over_a_set_frees_the_set() {
632        let mut d = db();
633        put(&mut d, b"a", b"v1");
634        d.sadd(b"s", [b"m1".as_ref()].into_iter()).expect("a set");
635        assert_eq!(d.sets.len(), 1);
636
637        assert_eq!(d.copy(b"a", b"s", true), Moved::Ok);
638        assert_eq!(d.sets.len(), 0, "the set went when the string arrived");
639        assert_eq!(d.kind_of(b"s"), Some(Kind::String));
640    }
641
642    /// `COPY` of a list, which used to take the server down with it.
643    ///
644    /// The catch all arm at the bottom of `export` was written when a set and a
645    /// hash were the only bodies there were, and the list and the sorted set
646    /// arrived past it without anybody coming back here. So `COPY mylist other`
647    /// reached `unreachable!` and panicked the shard, from a command any client
648    /// can send, against a type the server otherwise supports completely.
649    ///
650    /// The copy has to be a copy and not a second name for the same body, which
651    /// is the other half of what this checks: pushing to the destination must
652    /// not show up in the source.
653    #[test]
654    fn a_list_can_be_copied_and_the_copy_is_its_own() {
655        let mut d = db();
656        d.push(b"l", End::Left, [b"a".as_ref(), b"b".as_ref()].into_iter())
657            .expect("a list");
658
659        assert_eq!(d.copy(b"l", b"m", false), Moved::Ok);
660        assert_eq!(d.kind_of(b"m"), Some(Kind::List));
661        assert_eq!(d.llen(b"m").expect("a list"), 2);
662
663        d.push(b"m", End::Left, [b"c".as_ref()].into_iter())
664            .expect("a list");
665        assert_eq!(d.llen(b"l").expect("a list"), 2, "the source did not grow");
666        assert_eq!(d.llen(b"m").expect("a list"), 3);
667    }
668
669    /// The same for a sorted set, which had the same hole for the same reason.
670    #[test]
671    fn a_zset_can_be_copied_and_the_copy_is_its_own() {
672        let mut d = db();
673        d.zadd(b"z", [(1.0, b"m1".as_ref())].into_iter(), ZAdd::default())
674            .expect("a zset");
675
676        assert_eq!(d.copy(b"z", b"y", false), Moved::Ok);
677        assert_eq!(d.kind_of(b"y"), Some(Kind::Zset));
678        assert_eq!(d.zscore(b"y", b"m1").expect("a zset"), Some(1.0));
679
680        d.zadd(b"y", [(2.0, b"m2".as_ref())].into_iter(), ZAdd::default())
681            .expect("a zset");
682        assert_eq!(d.zcard(b"z").expect("a zset"), 1, "the source did not grow");
683        assert_eq!(d.zcard(b"y").expect("a zset"), 2);
684    }
685
686    /// A copy over a key that held a list gives the list back.
687    ///
688    /// The leak this guards against is the same one the set version guards
689    /// against: a record written over a body that nothing freed leaves a slab
690    /// slot reachable and never reused, and nothing about the server looks wrong
691    /// afterwards.
692    #[test]
693    fn copying_over_a_list_frees_the_list() {
694        let mut d = db();
695        put(&mut d, b"a", b"v1");
696        d.push(b"l", End::Left, [b"x".as_ref()].into_iter())
697            .expect("a list");
698
699        assert_eq!(d.copy(b"a", b"l", true), Moved::Ok);
700        assert_eq!(d.kind_of(b"l"), Some(Kind::String));
701        assert_eq!(read(&mut d, b"l"), b"v1");
702    }
703
704    #[test]
705    fn touch_counts_the_way_exists_counts() {
706        let mut d = db();
707        put(&mut d, b"a", b"v1");
708        put(&mut d, b"b", b"v2");
709
710        assert_eq!(d.touch([b"a".as_ref()].into_iter()), 1);
711        assert_eq!(d.touch([b"a".as_ref(), b"b".as_ref()].into_iter()), 2);
712        assert_eq!(d.touch([b"a".as_ref(), b"a".as_ref()].into_iter()), 2);
713        assert_eq!(d.touch([b"a".as_ref(), b"z".as_ref()].into_iter()), 1);
714        assert_eq!(d.touch([b"z".as_ref()].into_iter()), 0);
715    }
716}