Skip to main content

yo_kv/
hashes.rs

1//! The hash commands.
2//!
3//! One method per Redis command on [`Keyspace`], the same arrangement the set
4//! and string commands use. The hash itself, and the choice between the two
5//! representations it can be in, is [`crate::hash`]. This file is what the wire
6//! and the embedded API both call.
7//!
8//! # Where a hash lives
9//!
10//! Exactly where a set lives. The record under the key holds a type tag and four
11//! bytes saying which slot of the database's hash slab the body is in, and
12//! reaching it is one key lookup and one dependent load. The two slabs are
13//! separate rather than one slab of an enum, because the record's tag already
14//! says which one to look in and a discriminant on the body would be a second
15//! copy of a fact that is already there.
16//!
17//! The same two invariants hold, and both are about not leaking. Every path that
18//! deletes a key goes through `drop_key` and every path that writes over one
19//! goes through `free_body`. And a hash that loses its last field is deleted
20//! rather than stored empty, because an empty hash does not exist in Redis:
21//! `HDEL` taking the last field makes `EXISTS` answer zero.
22//!
23//! # Returning a field's value
24//!
25//! A value in the listpack band may be stored as an integer, so there is no
26//! `&[u8]` to hand back for it without writing the digits somewhere first. The
27//! reading commands take a closure and hand it a [`Text`] instead, which the
28//! reply layer formats straight into the output buffer. That is Y18, and it is
29//! why `HGET` is not simply `-> Option<&[u8]>`.
30//!
31//! # Errors
32//!
33//! Every command here answers `WRONGTYPE` for a key holding something that is
34//! not a hash, and treats a missing key as an empty one.
35
36use yo_common::num::{parse_f64, parse_i64};
37use yo_common::{Code, Error, Result};
38
39use crate::hash::{Hash, Text};
40use crate::keyspace::Keyspace;
41use crate::scan::Cursor;
42use crate::strings;
43use crate::ttl::{self, Applied, Ask, Cond};
44use crate::value::{self, Kind};
45
46/// What Redis says when a field does not hold a number.
47const NOT_AN_INT: &str = "hash value is not an integer";
48/// And when it does not hold a float.
49const NOT_A_FLOAT: &str = "hash value is not a float";
50/// And when the sum leaves the range.
51const WOULD_OVERFLOW: &str = "increment or decrement would overflow";
52/// And when a field deadline lands past the year it stops fitting.
53const BAD_EXPIRE: &str = "invalid expire time, must be >= 0";
54
55impl Keyspace {
56    /// `HSET key field value [field value ...]`. Answers how many were new.
57    ///
58    /// The pairs arrive as an iterator for the reason `SADD`'s members do: the
59    /// wire layer has them as positions in the connection's read buffer, and
60    /// collecting them into a slice first would be an allocation per command on
61    /// a shard thread.
62    ///
63    /// Redis's parser rejects an odd number of arguments before this is reached.
64    /// The embedded API has no parser in front of it, so an empty iterator does
65    /// not create the key, the same guard `SADD` has.
66    pub fn hset<'a>(
67        &mut self,
68        key: &[u8],
69        pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
70    ) -> Result<usize> {
71        for (f, v) in pairs.clone() {
72            strings::check_len(key, f.len())?;
73            strings::check_len(key, v.len())?;
74        }
75        let at = match self.hash_slot(key)? {
76            Some(at) => at,
77            None => {
78                if pairs.clone().next().is_none() {
79                    return Ok(0);
80                }
81                let hint = pairs.clone().count();
82                self.new_hash(key, hint)
83            }
84        };
85
86        // Copied out so the body can be borrowed mutably for the whole loop
87        // rather than once a pair.
88        let limits = self.hash_limits;
89        let hash = self
90            .hashes
91            .get_mut(at)
92            .expect("the record points at its body");
93        let mut added = 0;
94        for (field, value) in pairs {
95            if hash.set(field, value, &limits) {
96                added += 1;
97            }
98        }
99        Ok(added)
100    }
101
102    /// `HSETNX key field value`. Answers whether it was written.
103    ///
104    /// Unlike `SETNX` this is per field and not per key, so it writes into a
105    /// hash that already exists as long as that one field is missing.
106    pub fn hsetnx(&mut self, key: &[u8], field: &[u8], value: &[u8]) -> Result<bool> {
107        strings::check_len(key, field.len())?;
108        strings::check_len(key, value.len())?;
109        let at = match self.hash_slot(key)? {
110            Some(at) => {
111                if self.hash_at(at).contains(field) {
112                    return Ok(false);
113                }
114                at
115            }
116            None => self.new_hash(key, 1),
117        };
118        let limits = self.hash_limits;
119        self.hashes
120            .get_mut(at)
121            .expect("the record points at its body")
122            .set(field, value, &limits);
123        Ok(true)
124    }
125
126    /// `HGET key field`, as a borrow rather than a copy.
127    ///
128    /// `f` is handed `None` for a missing key and for a missing field alike,
129    /// because both are a nil reply and the caller has no reason to tell them
130    /// apart. `HEXISTS` is the command that does.
131    pub fn hget<R>(
132        &mut self,
133        key: &[u8],
134        field: &[u8],
135        f: impl FnOnce(Option<Text<'_>>) -> R,
136    ) -> Result<R> {
137        let Some(at) = self.hash_slot(key)? else {
138            return Ok(f(None));
139        };
140        Ok(f(self.hash_at(at).get(field)))
141    }
142
143    /// `HMGET key field [field ...]`, one call of `f` per field asked for.
144    ///
145    /// Every field gets a call, including the ones that are not there, because
146    /// the reply is positional: a client sending three fields gets three
147    /// entries back and matches them up by position. A missing key answers all
148    /// nils rather than an empty array for the same reason.
149    pub fn hmget<'a, F>(
150        &mut self,
151        key: &[u8],
152        fields: impl Iterator<Item = &'a [u8]>,
153        mut f: F,
154    ) -> Result<()>
155    where
156        F: FnMut(Option<Text<'_>>),
157    {
158        let slot = self.hash_slot(key)?;
159        for field in fields {
160            match slot {
161                Some(at) => f(self.hash_at(at).get(field)),
162                None => f(None),
163            }
164        }
165        Ok(())
166    }
167
168    /// `HDEL key field [field ...]`. Answers how many were there.
169    ///
170    /// The key goes when the last field does.
171    pub fn hdel<'a>(
172        &mut self,
173        key: &[u8],
174        fields: impl Iterator<Item = &'a [u8]>,
175    ) -> Result<usize> {
176        let Some(at) = self.hash_slot(key)? else {
177            return Ok(0);
178        };
179        let hash = self
180            .hashes
181            .get_mut(at)
182            .expect("the record points at its body");
183        let mut gone = 0;
184        for field in fields {
185            if hash.remove(field) {
186                gone += 1;
187            }
188        }
189        if hash.is_empty() {
190            self.drop_key(key);
191        }
192        Ok(gone)
193    }
194
195    /// `HEXPIREAT` and the three commands that turn into it.
196    ///
197    /// `at` is an absolute unix millisecond, which is what `HEXPIRE`,
198    /// `HPEXPIRE` and `HEXPIREAT` all become before they get here, and one call
199    /// of `f` happens per field asked for because the reply is positional.
200    ///
201    /// The deadline is checked against [`ttl::MAX_AT`] before any field is
202    /// touched, because Redis rejects the whole command rather than failing
203    /// field by field, and a command that names ten fields either sets all ten
204    /// or errors.
205    ///
206    /// A key that is not there answers [`Applied::Missing`] for every field,
207    /// which is the -2 Redis replies, because a missing key and an empty hash
208    /// are the same thing. The key goes when the last field does, which happens
209    /// when the deadline given has already passed.
210    pub fn hexpire<'a, F>(
211        &mut self,
212        key: &[u8],
213        at: u64,
214        cond: Cond,
215        fields: impl Iterator<Item = &'a [u8]>,
216        mut f: F,
217    ) -> Result<()>
218    where
219        F: FnMut(Applied),
220    {
221        if !ttl::valid_at(at) {
222            return Err(Error::new(Code::Invalid, BAD_EXPIRE));
223        }
224        let Some(slot) = self.hash_slot(key)? else {
225            for _ in fields {
226                f(Applied::Missing);
227            }
228            return Ok(());
229        };
230        let now = self.clock.now_ms();
231        let mut emptied = false;
232        for field in fields {
233            let hash = self.hash_at_mut(slot);
234            let applied = hash.expire(field, at, cond, now);
235            emptied = hash.is_empty();
236            f(applied);
237        }
238        if emptied {
239            self.drop_key(key);
240        }
241        Ok(())
242    }
243
244    /// `HTTL` and its relatives, one call of `f` per field asked for.
245    ///
246    /// What comes back is when the deadline falls due. Turning that into what is
247    /// left, and into seconds where the command asks for seconds, is the reply
248    /// layer's job, because [`Ask::remaining_ms`] is where that arithmetic lives
249    /// and it needs the moment being asked at.
250    pub fn httl<'a, F>(
251        &mut self,
252        key: &[u8],
253        fields: impl Iterator<Item = &'a [u8]>,
254        mut f: F,
255    ) -> Result<()>
256    where
257        F: FnMut(Ask),
258    {
259        let slot = self.hash_slot(key)?;
260        for field in fields {
261            match slot {
262                Some(at) => f(self.hash_at(at).deadline(field)),
263                None => f(Ask::Missing),
264            }
265        }
266        Ok(())
267    }
268
269    /// `HPERSIST key FIELDS numfields field [field ...]`.
270    ///
271    /// [`Ask::At`] means the deadline that was there has been taken off, which
272    /// the reply layer reports as 1.
273    pub fn hpersist<'a, F>(
274        &mut self,
275        key: &[u8],
276        fields: impl Iterator<Item = &'a [u8]>,
277        mut f: F,
278    ) -> Result<()>
279    where
280        F: FnMut(Ask),
281    {
282        let slot = self.hash_slot(key)?;
283        for field in fields {
284            match slot {
285                Some(at) => f(self.hash_at_mut(at).persist(field)),
286                None => f(Ask::Missing),
287            }
288        }
289        Ok(())
290    }
291
292    /// `HGETDEL key FIELDS numfields field [field ...]`.
293    ///
294    /// The value goes out and the field goes away, in that order, which is the
295    /// whole command: a client that wants both without a race would otherwise
296    /// send `HGET` and `HDEL` and hope. One call of `f` per field asked for,
297    /// including the ones that were not there, because the reply is positional
298    /// the way `HMGET`'s is.
299    ///
300    /// The key goes when the last field does.
301    pub fn hgetdel<'a, F>(
302        &mut self,
303        key: &[u8],
304        fields: impl Iterator<Item = &'a [u8]>,
305        mut f: F,
306    ) -> Result<()>
307    where
308        F: FnMut(Option<Text<'_>>),
309    {
310        let Some(slot) = self.hash_slot(key)? else {
311            for _ in fields {
312                f(None);
313            }
314            return Ok(());
315        };
316        for field in fields {
317            let hash = self.hash_at_mut(slot);
318            f(hash.get(field));
319            hash.remove(field);
320        }
321        if self.hash_at(slot).is_empty() {
322            self.drop_key(key);
323        }
324        Ok(())
325    }
326
327    /// `HGETEX key [EX s | PX ms | EXAT ts | PXAT ts | PERSIST] FIELDS ...`.
328    ///
329    /// The read and the deadline change in one command, which is what makes it
330    /// worth having: a plain `HSET` clears the deadline on the field it writes,
331    /// so there is no way to touch a field's expiry and see its value with the
332    /// commands that were there before.
333    ///
334    /// [`strings::Expire::Keep`] is a plain `HGETEX` with no option, and it is
335    /// the default here rather than `Clear`, which is the one place this
336    /// disagrees with `SET`. `Clear` is `PERSIST` and `At` is the other four.
337    ///
338    /// A deadline that has already gone deletes the field, and the value still
339    /// goes out, because the read happened first. The key goes with the last
340    /// field.
341    pub fn hgetex<'a, F>(
342        &mut self,
343        key: &[u8],
344        expire: strings::Expire,
345        fields: impl Iterator<Item = &'a [u8]>,
346        mut f: F,
347    ) -> Result<()>
348    where
349        F: FnMut(Option<Text<'_>>),
350    {
351        // Before anything is read, because Redis rejects the whole command
352        // rather than expiring the fields it got to first.
353        check_at(expire)?;
354        let Some(slot) = self.hash_slot(key)? else {
355            for _ in fields {
356                f(None);
357            }
358            return Ok(());
359        };
360        let now = self.clock.now_ms();
361        for field in fields {
362            let hash = self.hash_at_mut(slot);
363            f(hash.get(field));
364            match expire {
365                strings::Expire::Keep => {}
366                strings::Expire::Clear => {
367                    hash.persist(field);
368                }
369                // A deadline that has already gone answers Deleted and takes the
370                // field with it, which needs nothing here: the value went out
371                // above, before the field did, and the empty check below is
372                // what notices if that was the last one.
373                strings::Expire::At(at) => {
374                    hash.expire(field, at, Cond::Always, now);
375                }
376            }
377        }
378        if self.hash_at(slot).is_empty() {
379            self.drop_key(key);
380        }
381        Ok(())
382    }
383
384    /// `HSETEX key [FNX | FXX] [EX .. | KEEPTTL] FIELDS n field value [..]`.
385    ///
386    /// Answers whether it wrote, which is all of it or none of it. `FNX` wants
387    /// every field named to be missing and `FXX` wants every one of them to be
388    /// there, so a list where one field disagrees writes nothing at all. That is
389    /// stricter than `HSETNX`, which is per field, and it is what makes this
390    /// usable as a compare and set over a group of fields.
391    ///
392    /// [`strings::Expire::Clear`] is a plain `HSETEX` and is the default, since
393    /// a write clears the deadline on the field it writes anyway. `Keep` is
394    /// `KEEPTTL` and has to put the deadline back afterwards for that reason.
395    ///
396    /// A deadline that has already gone still answers written, unlike the
397    /// `HEXPIRE` family which has a separate code for it. The fields are stored
398    /// and then removed, and if that empties the hash the key goes too, so
399    /// `HSETEX key EXAT 1` on a key that did not exist leaves it not existing.
400    pub fn hsetex<'a>(
401        &mut self,
402        key: &[u8],
403        exists: strings::Exists,
404        expire: strings::Expire,
405        pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
406    ) -> Result<bool> {
407        for (f, v) in pairs.clone() {
408            strings::check_len(key, f.len())?;
409            strings::check_len(key, v.len())?;
410        }
411        check_at(expire)?;
412
413        let slot = self.hash_slot(key)?;
414        // The condition is answered before a single field is written, because
415        // it is about the whole list. A key that is not there has every field
416        // missing, so FXX fails on it and FNX passes without creating it yet.
417        let met = match exists {
418            strings::Exists::Always => true,
419            strings::Exists::IfMissing => {
420                slot.is_none_or(|at| pairs.clone().all(|(f, _)| !self.hash_at(at).contains(f)))
421            }
422            strings::Exists::IfPresent => {
423                slot.is_some_and(|at| pairs.clone().all(|(f, _)| self.hash_at(at).contains(f)))
424            }
425        };
426        if !met {
427            return Ok(false);
428        }
429        let slot = match slot {
430            Some(at) => at,
431            None => {
432                if pairs.clone().next().is_none() {
433                    return Ok(false);
434                }
435                self.new_hash(key, pairs.clone().count())
436            }
437        };
438
439        let limits = self.hash_limits;
440        let now = self.clock.now_ms();
441        for (field, value) in pairs {
442            let hash = self.hash_at_mut(slot);
443            // KEEPTTL has to read the deadline first, because the write is what
444            // clears it. There is no band where the value can be replaced with
445            // the deadline left alone, and adding one would be a second way to
446            // write a field.
447            let kept = match expire {
448                strings::Expire::Keep => hash.deadline(field),
449                _ => Ask::Missing,
450            };
451            hash.set(field, value, &limits);
452            match expire {
453                strings::Expire::Clear => {}
454                strings::Expire::Keep => {
455                    if let Ask::At(at) = kept {
456                        hash.expire(field, at, Cond::Always, now);
457                    }
458                }
459                strings::Expire::At(at) => {
460                    hash.expire(field, at, Cond::Always, now);
461                }
462            }
463        }
464        if self.hash_at(slot).is_empty() {
465            self.drop_key(key);
466        }
467        Ok(true)
468    }
469
470    /// `HLEN key`.
471    pub fn hlen(&mut self, key: &[u8]) -> Result<usize> {
472        match self.hash_slot(key)? {
473            Some(at) => Ok(self.hash_at(at).len()),
474            None => Ok(0),
475        }
476    }
477
478    /// `HEXISTS key field`.
479    pub fn hexists(&mut self, key: &[u8], field: &[u8]) -> Result<bool> {
480        match self.hash_slot(key)? {
481            Some(at) => Ok(self.hash_at(at).contains(field)),
482            None => Ok(false),
483        }
484    }
485
486    /// `HSTRLEN key field`, without writing the value anywhere.
487    ///
488    /// A value held as an integer answers with how many digits it would take,
489    /// counted rather than formatted, which is what [`Text::byte_len`] is for.
490    pub fn hstrlen(&mut self, key: &[u8], field: &[u8]) -> Result<usize> {
491        match self.hash_slot(key)? {
492            Some(at) => Ok(self.hash_at(at).value_len(field).unwrap_or(0)),
493            None => Ok(0),
494        }
495    }
496
497    /// `HGETALL key`, `HKEYS key` and `HVALS key`, which differ only in what
498    /// the caller does with each pair.
499    ///
500    /// One method for the three because the walk is the whole of the work and
501    /// three copies of it would be three chances for one of them to drift. The
502    /// caller taking a pair and using half of it costs nothing, since neither
503    /// half is formatted until something asks for it.
504    ///
505    /// `Ok(false)` means the key was not there, which is an empty reply for all
506    /// three and never a nil.
507    pub fn hgetall<F>(&mut self, key: &[u8], mut f: F) -> Result<bool>
508    where
509        F: FnMut(Text<'_>, Text<'_>),
510    {
511        self.with_hash(key, |hash| match hash {
512            Some(h) => {
513                for (field, value) in h.iter() {
514                    f(field, value);
515                }
516                true
517            }
518            None => false,
519        })
520    }
521
522    /// Hand the hash under `key` to `f`, or hand it `None` if there is no key.
523    ///
524    /// The same thing [`Keyspace::with_set`] is for, and here it matters more.
525    /// `HGETALL` on RESP3 answers a map, whose header carries the pair count, so
526    /// the wire layer needs the length and then the pairs. Going back through
527    /// [`Keyspace::hlen`] for the header would be a second key lookup on the
528    /// command that is most likely to be in a loop.
529    ///
530    /// A callback rather than a returned `&Hash` because the reap happens under
531    /// `&mut self` and a borrow carved out of that cannot outlive the call.
532    pub fn with_hash<R>(&mut self, key: &[u8], f: impl FnOnce(Option<&Hash>) -> R) -> Result<R> {
533        let at = self.hash_slot(key)?;
534        Ok(f(at.map(|at| self.hash_at(at))))
535    }
536
537    /// `HSCAN key cursor [COUNT n]`, with the cursor to resume from.
538    ///
539    /// `NOVALUES` is the caller's business: it gets both halves and drops the
540    /// one it does not want, exactly as `HKEYS` does.
541    pub fn hscan<F>(&mut self, key: &[u8], cursor: Cursor, count: usize, f: F) -> Result<Cursor>
542    where
543        F: FnMut(Text<'_>, Text<'_>),
544    {
545        let Some(at) = self.hash_slot(key)? else {
546            return Ok(Cursor::END);
547        };
548        Ok(self.hash_at(at).scan(cursor, count, f))
549    }
550
551    /// `HINCRBY key field increment`. Answers the sum.
552    ///
553    /// A field that is not there counts as zero and is created, which is what
554    /// makes this the counter primitive it is used as. A field holding
555    /// something that is not an integer is an error and leaves the hash exactly
556    /// as it was, and so is a sum that leaves the range: Redis checks the
557    /// overflow before the write rather than wrapping and storing the wrap.
558    pub fn hincrby(&mut self, key: &[u8], field: &[u8], by: i64) -> Result<i64> {
559        strings::check_len(key, field.len())?;
560        let at = match self.hash_slot(key)? {
561            Some(at) => at,
562            None => self.new_hash(key, 1),
563        };
564        let current = match self.hash_at(at).get(field) {
565            Some(Text::Int(n)) => n,
566            Some(Text::Str(s)) => {
567                parse_i64(s).ok_or_else(|| Error::new(Code::Invalid, NOT_AN_INT))?
568            }
569            None => 0,
570        };
571        let next = current
572            .checked_add(by)
573            .ok_or_else(|| Error::new(Code::Invalid, WOULD_OVERFLOW))?;
574
575        let mut buf = [0u8; yo_common::num::DIGITS_MAX];
576        let text = yo_common::num::i64_digits(&mut buf, next);
577        let limits = self.hash_limits;
578        self.hashes
579            .get_mut(at)
580            .expect("the record points at its body")
581            .set(field, text, &limits);
582        Ok(next)
583    }
584
585    /// `HINCRBYFLOAT key field increment`. Answers the sum.
586    ///
587    /// The same rules with the float versions of the errors. An infinite
588    /// increment is not refused up front, for the reason `INCRBYFLOAT` gives:
589    /// Redis parses it, does the addition and then reports that the result is
590    /// not finite, so `HINCRBYFLOAT k f inf` says the increment would produce
591    /// infinity and not that the increment is not a float.
592    pub fn hincrbyfloat(&mut self, key: &[u8], field: &[u8], by: f64) -> Result<f64> {
593        strings::check_len(key, field.len())?;
594        let at = match self.hash_slot(key)? {
595            Some(at) => at,
596            None => self.new_hash(key, 1),
597        };
598        let current = match self.hash_at(at).get(field) {
599            Some(Text::Int(n)) => n as f64,
600            Some(Text::Str(s)) => {
601                parse_f64(s).ok_or_else(|| Error::new(Code::Invalid, NOT_A_FLOAT))?
602            }
603            None => 0.0,
604        };
605        let next = current + by;
606        if !next.is_finite() {
607            return Err(Error::new(
608                Code::Invalid,
609                "increment would produce NaN or Infinity",
610            ));
611        }
612
613        let mut buf = [0u8; yo_common::num::DOUBLE_MAX];
614        let text = yo_common::num::write_double(&mut buf, next);
615        let limits = self.hash_limits;
616        self.hashes
617            .get_mut(at)
618            .expect("the record points at its body")
619            .set(field, text, &limits);
620        Ok(next)
621    }
622
623    /// `HRANDFIELD key`, as a borrow.
624    ///
625    /// `f` is handed `None` when the key is not there, which is a nil and not
626    /// an empty reply.
627    pub fn hrandfield<R>(
628        &mut self,
629        key: &[u8],
630        f: impl FnOnce(Option<(Text<'_>, Text<'_>)>) -> R,
631    ) -> Result<R> {
632        let Some(at) = self.hash_slot(key)? else {
633            return Ok(f(None));
634        };
635        let pick = self.rng.below(self.hash_at(at).len());
636        Ok(f(self.hash_at(at).at(pick)))
637    }
638
639    /// `HRANDFIELD key count`, which is two commands wearing one name.
640    ///
641    /// A negative count is the with repeats form: exactly that many fields,
642    /// drawn one at a time, and the same field can come back more than once. It
643    /// is the only form that can answer more fields than the hash holds.
644    ///
645    /// A positive count is distinct fields, at most as many as the hash holds.
646    /// `SRANDMEMBER` splits its distinct form two ways because a set can be
647    /// millions of members and drawing three of them should not walk all of
648    /// them. A hash draws differently: Redis's own `HRANDFIELD` with a positive
649    /// count builds the whole answer either way, so this walks the fields once
650    /// and takes each with the probability that leaves the right number at the
651    /// end. That is Knuth's selection sampling, it needs no memory at all, and
652    /// it is `O(len)` rather than `O(count)`.
653    ///
654    /// A shuffle is deliberately not done. Redis does not promise an order here
655    /// and the walk order is not the insertion order once a field has been
656    /// removed, so shuffling would buy a guarantee nobody is owed at the price
657    /// of an allocation.
658    pub fn hrandfield_n<F>(&mut self, key: &[u8], count: i64, mut f: F) -> Result<()>
659    where
660        F: FnMut(Text<'_>, Text<'_>),
661    {
662        let Some(at) = self.hash_slot(key)? else {
663            return Ok(());
664        };
665        // Borrowed apart rather than through `hash_at`, because drawing and
666        // reading have to be alive at the same time and a method taking `&self`
667        // would hold the whole database.
668        let rng = &mut self.rng;
669        let hash = self.hashes.get(at).expect("the record points at its body");
670        let len = hash.len();
671
672        let Ok(want) = usize::try_from(count) else {
673            let repeats = usize::try_from(count.unsigned_abs()).unwrap_or(usize::MAX);
674            for _ in 0..repeats {
675                let (field, value) = hash
676                    .at(rng.below(len))
677                    .expect("the draw was under the length");
678                f(field, value);
679            }
680            return Ok(());
681        };
682
683        let mut left = want.min(len);
684        let mut seen = len;
685        for i in 0..len {
686            if left == 0 {
687                break;
688            }
689            // Take this one with probability left/seen, which is what leaves
690            // exactly `left` taken by the end whatever the draws come out as.
691            if rng.below(seen) < left {
692                let (field, value) = hash.at(i).expect("i is under the length");
693                f(field, value);
694                left -= 1;
695            }
696            seen -= 1;
697        }
698        Ok(())
699    }
700
701    // ------------------------------------------------------------------ inside
702
703    /// The slot `key`'s hash is in, or `None` if there is no such key.
704    ///
705    /// This is the one place a hash command finds its body, so it is the one
706    /// place that has to reap first and answer `WRONGTYPE` for another type.
707    fn hash_slot(&mut self, key: &[u8]) -> Result<Option<u32>> {
708        let Some(at) = self.live_slot(key, Kind::Hash)? else {
709            return Ok(None);
710        };
711        // And now the fields, which is the second half of lazy expiry. It runs
712        // here rather than in every command so that there is one place a hash
713        // becomes live, and it is a load and a comparison on a hash that has
714        // never been given a field deadline, which is nearly all of them.
715        let now = self.clock.now_ms();
716        let hash = self
717            .hashes
718            .get_mut(at)
719            .expect("the record points at its body");
720        if hash.reap(now) > 0 && hash.is_empty() {
721            // The last field expiring deletes the key, exactly as the last HDEL
722            // does, because an empty hash is not a thing Redis stores.
723            self.drop_key(key);
724            return Ok(None);
725        }
726        Ok(Some(at))
727    }
728
729    /// The body in a slot the record pointed at, to be written.
730    #[inline]
731    fn hash_at_mut(&mut self, at: u32) -> &mut Hash {
732        self.hashes
733            .get_mut(at)
734            .expect("the record points at its body")
735    }
736
737    /// The body in a slot the record pointed at.
738    ///
739    /// Panicking here means a record outlived its body, which is the one bug the
740    /// slab deliberately does not carry a generation counter to catch, so this
741    /// is where it would be caught instead.
742    #[inline]
743    fn hash_at(&self, at: u32) -> &Hash {
744        self.hashes.get(at).expect("the record points at its body")
745    }
746
747    /// Make an empty hash under `key` and answer which slot it went in.
748    ///
749    /// The hint only picks the representation to start in, so that an `HSET`
750    /// with a thousand pairs builds a table once instead of filling a listpack
751    /// and then converting it.
752    fn new_hash(&mut self, key: &[u8], hint: usize) -> u32 {
753        // The body and, every so often, the slab that holds it. See
754        // `yo_alloc::first_touch` for why this is the one allocation a command
755        // is allowed to make.
756        let at =
757            yo_alloc::first_touch(|| self.hashes.insert(Hash::with_hint(hint, &self.hash_limits)));
758        let len = value::slot_record_len(false);
759        self.write_rec(key, len, |out| {
760            value::write_slot_record(out, Kind::Hash, at, None);
761        });
762        self.bodies += 1;
763        at
764    }
765}
766
767/// Refuses a deadline past the ceiling before the command touches anything.
768///
769/// Both `HGETEX` and `HSETEX` take the deadline as an option rather than as the
770/// argument it is in the `HEXPIRE` family, and both have to answer for it
771/// before they have read or written a field, since Redis refuses the whole
772/// command rather than half doing it.
773fn check_at(expire: strings::Expire) -> Result<()> {
774    match expire {
775        strings::Expire::At(at) if !ttl::valid_at(at) => Err(Error::new(Code::Invalid, BAD_EXPIRE)),
776        _ => Ok(()),
777    }
778}
779
780#[cfg(test)]
781mod tests {
782    use super::*;
783    use crate::Clock;
784    use crate::hash::Encoding;
785
786    fn db() -> Keyspace {
787        Keyspace::with_clock(Clock::fixed(1_000))
788    }
789
790    fn set(d: &mut Keyspace, key: &[u8], pairs: &[(&[u8], &[u8])]) -> usize {
791        d.hset(key, pairs.iter().copied()).expect("a hash")
792    }
793
794    fn get(d: &mut Keyspace, key: &[u8], field: &[u8]) -> Option<String> {
795        d.hget(key, field, |t| t.map(|t| text(&t))).expect("a hash")
796    }
797
798    fn text(t: &Text<'_>) -> String {
799        String::from_utf8(t.to_vec()).expect("utf8 in these tests")
800    }
801
802    fn all(d: &mut Keyspace, key: &[u8]) -> Vec<(String, String)> {
803        let mut out = Vec::new();
804        d.hgetall(key, |f, v| out.push((text(&f), text(&v))))
805            .expect("a hash");
806        out.sort();
807        out
808    }
809
810    fn expire(d: &mut Keyspace, key: &[u8], at: u64, fields: &[&[u8]]) -> Vec<Applied> {
811        let mut out = Vec::new();
812        d.hexpire(key, at, Cond::Always, fields.iter().copied(), |a| {
813            out.push(a);
814        })
815        .expect("a hash");
816        out
817    }
818
819    fn ttl_of(d: &mut Keyspace, key: &[u8], fields: &[&[u8]]) -> Vec<Ask> {
820        let mut out = Vec::new();
821        d.httl(key, fields.iter().copied(), |a| out.push(a))
822            .expect("a hash");
823        out
824    }
825
826    #[test]
827    fn setting_a_field_on_a_key_that_is_not_there_makes_it() {
828        let mut d = db();
829        assert_eq!(set(&mut d, b"h", &[(b"f", b"v")]), 1);
830        assert_eq!(d.kind_of(b"h"), Some(Kind::Hash));
831        assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("v"));
832    }
833
834    #[test]
835    fn writing_a_field_again_is_not_a_new_field() {
836        let mut d = db();
837        assert_eq!(set(&mut d, b"h", &[(b"f", b"one"), (b"g", b"two")]), 2);
838        assert_eq!(set(&mut d, b"h", &[(b"f", b"three")]), 0, "f was there");
839        assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("three"));
840        assert_eq!(d.hlen(b"h").expect("a hash"), 2);
841    }
842
843    #[test]
844    fn an_empty_write_does_not_make_a_key() {
845        let mut d = db();
846        let none: [(&[u8], &[u8]); 0] = [];
847        assert_eq!(d.hset(b"h", none.iter().copied()).expect("ok"), 0);
848        assert_eq!(d.kind_of(b"h"), None, "an empty hash does not exist");
849    }
850
851    #[test]
852    fn losing_the_last_field_loses_the_key() {
853        let mut d = db();
854        set(&mut d, b"h", &[(b"f", b"v"), (b"g", b"w")]);
855        assert_eq!(d.hdel(b"h", [b"f".as_slice()].into_iter()).expect("ok"), 1);
856        assert_eq!(d.kind_of(b"h"), Some(Kind::Hash), "g is still there");
857        assert_eq!(d.hdel(b"h", [b"g".as_slice()].into_iter()).expect("ok"), 1);
858        assert_eq!(d.kind_of(b"h"), None, "and now nothing is");
859        assert_eq!(d.len(), 0);
860    }
861
862    #[test]
863    fn every_command_says_wrongtype_for_a_string() {
864        let mut d = db();
865        d.set_plain(b"s", b"v").expect("room");
866
867        assert_eq!(
868            d.hset(b"s", [(b"f".as_slice(), b"v".as_slice())].into_iter())
869                .unwrap_err()
870                .code(),
871            Code::WrongType
872        );
873        assert!(d.hget(b"s", b"f", |_| ()).is_err());
874        assert!(d.hdel(b"s", [b"f".as_slice()].into_iter()).is_err());
875        assert!(d.hlen(b"s").is_err());
876        assert!(d.hexists(b"s", b"f").is_err());
877        assert!(d.hstrlen(b"s", b"f").is_err());
878        assert!(d.hgetall(b"s", |_, _| ()).is_err());
879        assert!(d.hsetnx(b"s", b"f", b"v").is_err());
880        assert!(d.hincrby(b"s", b"f", 1).is_err());
881        assert!(d.hincrbyfloat(b"s", b"f", 1.0).is_err());
882        assert!(d.hrandfield(b"s", |_| ()).is_err());
883        assert!(d.hrandfield_n(b"s", 1, |_, _| ()).is_err());
884        assert!(d.hscan(b"s", Cursor::START, 10, |_, _| ()).is_err());
885        assert!(
886            d.hmget(b"s", [b"f".as_slice()].into_iter(), |_| ())
887                .is_err()
888        );
889
890        assert_eq!(
891            d.kind_of(b"s"),
892            Some(Kind::String),
893            "and none of them wrote anything"
894        );
895    }
896
897    #[test]
898    fn a_missing_key_reads_as_an_empty_hash() {
899        let mut d = db();
900        assert_eq!(d.hlen(b"nope").expect("ok"), 0);
901        assert!(!d.hexists(b"nope", b"f").expect("ok"));
902        assert_eq!(d.hstrlen(b"nope", b"f").expect("ok"), 0);
903        assert_eq!(get(&mut d, b"nope", b"f"), None);
904        assert!(!d.hgetall(b"nope", |_, _| ()).expect("ok"));
905        assert_eq!(
906            d.hdel(b"nope", [b"f".as_slice()].into_iter()).expect("ok"),
907            0
908        );
909    }
910
911    #[test]
912    fn hmget_answers_once_per_field_asked_for() {
913        let mut d = db();
914        set(&mut d, b"h", &[(b"a", b"1"), (b"c", b"3")]);
915
916        let mut got = Vec::new();
917        d.hmget(b"h", [b"a".as_slice(), b"b", b"c"].into_iter(), |t| {
918            got.push(t.map(|t| text(&t)));
919        })
920        .expect("a hash");
921        assert_eq!(
922            got,
923            vec![Some("1".into()), None, Some("3".into())],
924            "the reply is positional, so b gets a nil and not a gap"
925        );
926
927        let mut missing = Vec::new();
928        d.hmget(b"gone", [b"a".as_slice(), b"b"].into_iter(), |t| {
929            missing.push(t.is_none());
930        })
931        .expect("no key");
932        assert_eq!(missing, vec![true, true], "a missing key is all nils");
933    }
934
935    #[test]
936    fn hsetnx_writes_only_a_field_that_is_not_there() {
937        let mut d = db();
938        assert!(d.hsetnx(b"h", b"f", b"one").expect("ok"), "made the key");
939        assert!(!d.hsetnx(b"h", b"f", b"two").expect("ok"), "f was there");
940        assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("one"));
941        assert!(
942            d.hsetnx(b"h", b"g", b"two").expect("ok"),
943            "and it is per field, not per key"
944        );
945        assert_eq!(d.hlen(b"h").expect("ok"), 2);
946    }
947
948    #[test]
949    fn hstrlen_counts_a_number_without_writing_it() {
950        let mut d = db();
951        set(&mut d, b"h", &[(b"n", b"-12345"), (b"s", b"hello")]);
952        assert_eq!(d.hstrlen(b"h", b"n").expect("ok"), 6);
953        assert_eq!(d.hstrlen(b"h", b"s").expect("ok"), 5);
954        assert_eq!(d.hstrlen(b"h", b"nope").expect("ok"), 0);
955    }
956
957    #[test]
958    fn incrementing_counts_up_from_nothing_and_refuses_what_is_not_a_number() {
959        let mut d = db();
960        assert_eq!(d.hincrby(b"h", b"n", 5).expect("ok"), 5, "absent is zero");
961        assert_eq!(d.hincrby(b"h", b"n", -7).expect("ok"), -2);
962        assert_eq!(get(&mut d, b"h", b"n").as_deref(), Some("-2"));
963
964        set(&mut d, b"h", &[(b"s", b"words")]);
965        let err = d.hincrby(b"h", b"s", 1).unwrap_err();
966        assert_eq!(err.code(), Code::Invalid);
967        assert_eq!(err.message(), NOT_AN_INT);
968        assert_eq!(
969            get(&mut d, b"h", b"s").as_deref(),
970            Some("words"),
971            "and it left the field alone"
972        );
973    }
974
975    #[test]
976    fn an_increment_that_leaves_the_range_is_refused_and_not_wrapped() {
977        let mut d = db();
978        let max = i64::MAX.to_string();
979        set(&mut d, b"h", &[(b"n", max.as_bytes())]);
980        let err = d.hincrby(b"h", b"n", 1).unwrap_err();
981        assert_eq!(err.message(), WOULD_OVERFLOW);
982        assert_eq!(
983            get(&mut d, b"h", b"n").as_deref(),
984            Some(max.as_str()),
985            "the field still holds what it held"
986        );
987    }
988
989    #[test]
990    fn incrementing_by_a_float_reports_the_sum_and_refuses_infinity() {
991        let mut d = db();
992        assert!((d.hincrbyfloat(b"h", b"f", 10.5).expect("ok") - 10.5).abs() < 1e-9);
993        assert!((d.hincrbyfloat(b"h", b"f", 0.1).expect("ok") - 10.6).abs() < 1e-9);
994
995        let err = d.hincrbyfloat(b"h", b"f", f64::INFINITY).unwrap_err();
996        assert_eq!(err.message(), "increment would produce NaN or Infinity");
997
998        set(&mut d, b"h", &[(b"s", b"words")]);
999        assert_eq!(
1000            d.hincrbyfloat(b"h", b"s", 1.0).unwrap_err().message(),
1001            NOT_A_FLOAT
1002        );
1003    }
1004
1005    #[test]
1006    fn a_hash_promotes_in_the_keyspace_and_object_encoding_says_so() {
1007        let mut d = db();
1008        set(&mut d, b"h", &[(b"f", b"v")]);
1009        assert_eq!(d.hash_encoding(b"h"), Some(Encoding::Listpack));
1010        assert_eq!(d.encoding_name(b"h"), Some("listpack"));
1011
1012        for i in 0..600u32 {
1013            let f = format!("field-{i}");
1014            set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1015        }
1016        assert_eq!(d.hash_encoding(b"h"), Some(Encoding::Hashtable));
1017        assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1018        assert_eq!(d.hlen(b"h").expect("ok"), 601);
1019        assert_eq!(
1020            d.hash_encoding(b"missing"),
1021            None,
1022            "and a key that is not a hash has no hash encoding"
1023        );
1024    }
1025
1026    #[test]
1027    fn a_hash_survives_being_given_a_deadline_and_goes_when_it_passes() {
1028        let mut d = db();
1029        set(&mut d, b"h", &[(b"f", b"v"), (b"g", b"w")]);
1030        assert!(d.set_expiry(b"h", Some(1_100)));
1031        assert_eq!(
1032            all(&mut d, b"h"),
1033            vec![("f".into(), "v".into()), ("g".into(), "w".into())],
1034            "writing the record did not touch the body"
1035        );
1036
1037        d.clock_mut().advance(100);
1038        assert_eq!(d.kind_of(b"h"), None);
1039        assert_eq!(d.len(), 0);
1040        assert_eq!(d.expired_keys(), 1);
1041    }
1042
1043    #[test]
1044    fn writing_a_string_over_a_hash_gives_the_body_back() {
1045        let mut d = db();
1046        for i in 0..300u32 {
1047            let f = format!("field-{i}");
1048            set(&mut d, b"h", &[(f.as_bytes(), b"a value of some length")]);
1049        }
1050        assert_eq!(d.hashes.len(), 1);
1051        let held = d.memory_bytes();
1052        d.set_plain(b"h", b"now a string").expect("room");
1053
1054        assert_eq!(d.kind_of(b"h"), Some(Kind::String));
1055        // The slot rather than the byte count, because the byte count is mostly
1056        // the arena and the arena does not give a segment back until it is
1057        // compacted. A body that kept its slot would be reachable forever and
1058        // is the exact leak `free_body` exists to stop.
1059        assert_eq!(d.hashes.len(), 0, "the body went with the record");
1060        assert!(d.memory_bytes() < held, "and its bytes went with it");
1061    }
1062
1063    #[test]
1064    fn a_scan_walks_a_hash_in_the_keyspace_exactly_once() {
1065        let mut d = db();
1066        for i in 0..500u32 {
1067            let f = format!("field-{i}");
1068            let v = format!("value-{i}");
1069            set(&mut d, b"h", &[(f.as_bytes(), v.as_bytes())]);
1070        }
1071
1072        let mut seen: Vec<(String, String)> = Vec::new();
1073        let mut cursor = Cursor::START;
1074        loop {
1075            cursor = d
1076                .hscan(b"h", cursor, 32, |f, v| seen.push((text(&f), text(&v))))
1077                .expect("a hash");
1078            if cursor == Cursor::END {
1079                break;
1080            }
1081        }
1082        seen.sort();
1083        seen.dedup();
1084        assert_eq!(seen.len(), 500, "every field once and only once");
1085        for (f, v) in &seen {
1086            assert_eq!(
1087                f.strip_prefix("field-"),
1088                v.strip_prefix("value-"),
1089                "and paired with its own value"
1090            );
1091        }
1092    }
1093
1094    #[test]
1095    fn a_draw_takes_the_count_asked_for_and_repeats_only_when_told_to() {
1096        let mut d = db();
1097        d.seed(7);
1098        for i in 0..10u32 {
1099            let f = format!("f{i}");
1100            set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1101        }
1102
1103        let mut got = Vec::new();
1104        d.hrandfield_n(b"h", 4, |f, _| got.push(text(&f)))
1105            .expect("ok");
1106        assert_eq!(got.len(), 4);
1107        got.sort();
1108        got.dedup();
1109        assert_eq!(got.len(), 4, "a positive count is distinct");
1110
1111        let mut over = Vec::new();
1112        d.hrandfield_n(b"h", 25, |f, _| over.push(text(&f)))
1113            .expect("ok");
1114        assert_eq!(over.len(), 10, "and never more than the hash holds");
1115
1116        let mut with_repeats = Vec::new();
1117        d.hrandfield_n(b"h", -25, |f, _| with_repeats.push(text(&f)))
1118            .expect("ok");
1119        assert_eq!(
1120            with_repeats.len(),
1121            25,
1122            "a negative count is exactly that many, repeats and all"
1123        );
1124
1125        let one = d
1126            .hrandfield(b"h", |p| p.map(|(f, _)| text(&f)))
1127            .expect("ok");
1128        assert!(one.is_some());
1129        assert!(
1130            d.hrandfield(b"gone", |p| p.is_none()).expect("ok"),
1131            "and a missing key draws a nil"
1132        );
1133    }
1134
1135    #[test]
1136    fn a_field_deadline_goes_on_and_is_reported_back() {
1137        let mut d = db();
1138        set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1139        assert_eq!(
1140            expire(&mut d, b"h", 5_000, &[b"a", b"nope"]),
1141            [Applied::Ok, Applied::Missing],
1142            "one call per field, in the order asked"
1143        );
1144        assert_eq!(
1145            ttl_of(&mut d, b"h", &[b"a", b"b", b"nope"]),
1146            [Ask::At(5_000), Ask::NoDeadline, Ask::Missing]
1147        );
1148        assert_eq!(
1149            d.encoding_name(b"h"),
1150            Some("listpackex"),
1151            "and the band widened to hold it"
1152        );
1153    }
1154
1155    #[test]
1156    fn a_field_is_gone_the_next_time_the_key_is_touched() {
1157        let mut d = db();
1158        set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1159        expire(&mut d, b"h", 2_000, &[b"a"]);
1160
1161        assert_eq!(d.hlen(b"h").expect("ok"), 2, "still there at 1000");
1162        d.clock_mut().advance(1_000);
1163        assert_eq!(d.hlen(b"h").expect("ok"), 1, "and gone at 2000");
1164        assert_eq!(get(&mut d, b"h", b"a"), None);
1165        assert_eq!(get(&mut d, b"h", b"b").as_deref(), Some("2"));
1166        assert_eq!(all(&mut d, b"h"), [("b".to_owned(), "2".to_owned())]);
1167    }
1168
1169    #[test]
1170    fn the_key_goes_when_its_last_field_expires() {
1171        let mut d = db();
1172        set(&mut d, b"h", &[(b"a", b"1")]);
1173        expire(&mut d, b"h", 2_000, &[b"a"]);
1174        assert_eq!(d.kind_of(b"h"), Some(Kind::Hash));
1175
1176        d.clock_mut().advance(1_000);
1177        assert_eq!(d.hlen(b"h").expect("ok"), 0);
1178        assert_eq!(d.kind_of(b"h"), None, "an empty hash is not stored");
1179        assert_eq!(d.len(), 0);
1180    }
1181
1182    /// `HEXPIRE key 0` is a roundabout `HDEL`, and taking the last field with it
1183    /// takes the key.
1184    #[test]
1185    fn a_deadline_already_past_deletes_the_field_now() {
1186        let mut d = db();
1187        set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1188        assert_eq!(expire(&mut d, b"h", 500, &[b"a"]), [Applied::Deleted]);
1189        assert_eq!(d.hlen(b"h").expect("ok"), 1);
1190
1191        assert_eq!(expire(&mut d, b"h", 500, &[b"b"]), [Applied::Deleted]);
1192        assert_eq!(d.kind_of(b"h"), None);
1193    }
1194
1195    #[test]
1196    fn persisting_puts_the_field_back_to_no_deadline() {
1197        let mut d = db();
1198        set(&mut d, b"h", &[(b"a", b"1")]);
1199        expire(&mut d, b"h", 5_000, &[b"a"]);
1200
1201        let mut out = Vec::new();
1202        d.hpersist(
1203            b"h",
1204            [b"a".as_slice(), b"nope".as_slice()].into_iter(),
1205            |a| {
1206                out.push(a);
1207            },
1208        )
1209        .expect("ok");
1210        assert_eq!(out, [Ask::At(5_000), Ask::Missing]);
1211        assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1212
1213        d.clock_mut().advance(100_000);
1214        assert_eq!(d.hlen(b"h").expect("ok"), 1, "and it outlives its deadline");
1215    }
1216
1217    #[test]
1218    fn a_missing_key_answers_no_field_for_every_field_it_was_asked() {
1219        let mut d = db();
1220        assert_eq!(
1221            expire(&mut d, b"gone", 5_000, &[b"a", b"b"]),
1222            [Applied::Missing, Applied::Missing]
1223        );
1224        assert_eq!(
1225            ttl_of(&mut d, b"gone", &[b"a", b"b"]),
1226            [Ask::Missing, Ask::Missing]
1227        );
1228        assert_eq!(d.kind_of(b"gone"), None, "and asking did not create it");
1229    }
1230
1231    #[test]
1232    fn a_deadline_past_the_ceiling_is_refused_before_any_field_moves() {
1233        let mut d = db();
1234        set(&mut d, b"h", &[(b"a", b"1")]);
1235        let err = d
1236            .hexpire(
1237                b"h",
1238                crate::ttl::MAX_AT + 1,
1239                Cond::Always,
1240                [b"a".as_slice()].into_iter(),
1241                |_| unreachable!("no field is reached"),
1242            )
1243            .expect_err("past the ceiling");
1244        assert_eq!(err.code(), Code::Invalid);
1245        assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1246    }
1247
1248    #[test]
1249    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
1250        let mut d = db();
1251        d.set_plain(b"s", b"v").expect("room");
1252        assert!(
1253            d.hexpire(
1254                b"s",
1255                5_000,
1256                Cond::Always,
1257                [b"a".as_slice()].into_iter(),
1258                |_| { unreachable!("nothing is reached") }
1259            )
1260            .is_err()
1261        );
1262        assert!(d.httl(b"s", [b"a".as_slice()].into_iter(), |_| {}).is_err());
1263        assert!(
1264            d.hpersist(b"s", [b"a".as_slice()].into_iter(), |_| {})
1265                .is_err()
1266        );
1267        assert_eq!(
1268            d.kind_of(b"s"),
1269            Some(Kind::String),
1270            "and the string is intact"
1271        );
1272    }
1273
1274    #[test]
1275    fn a_hash_that_never_expires_a_field_is_untouched_by_all_of_this() {
1276        let mut d = db();
1277        for i in 0..600u32 {
1278            set(&mut d, b"h", &[(format!("f{i}").as_bytes(), b"v")]);
1279        }
1280        assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1281        d.clock_mut().advance(1_000_000);
1282        assert_eq!(d.hlen(b"h").expect("ok"), 600, "nothing had a deadline");
1283    }
1284
1285    /// `HGETDEL`, as the strings it handed back.
1286    fn getdel(d: &mut Keyspace, key: &[u8], fields: &[&[u8]]) -> Vec<Option<String>> {
1287        let mut out = Vec::new();
1288        d.hgetdel(key, fields.iter().copied(), |t| {
1289            out.push(t.map(|t| text(&t)));
1290        })
1291        .expect("a hash");
1292        out
1293    }
1294
1295    /// `HGETEX`, the same way.
1296    fn getex(
1297        d: &mut Keyspace,
1298        key: &[u8],
1299        expire: strings::Expire,
1300        fields: &[&[u8]],
1301    ) -> Vec<Option<String>> {
1302        let mut out = Vec::new();
1303        d.hgetex(key, expire, fields.iter().copied(), |t| {
1304            out.push(t.map(|t| text(&t)));
1305        })
1306        .expect("a hash");
1307        out
1308    }
1309
1310    /// `HSETEX`, with the two options spelled out.
1311    fn setex(
1312        d: &mut Keyspace,
1313        key: &[u8],
1314        exists: strings::Exists,
1315        expire: strings::Expire,
1316        pairs: &[(&[u8], &[u8])],
1317    ) -> bool {
1318        d.hsetex(key, exists, expire, pairs.iter().copied())
1319            .expect("a hash")
1320    }
1321
1322    #[test]
1323    fn getdel_hands_the_value_back_and_then_takes_the_field() {
1324        let mut d = db();
1325        set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")]);
1326        assert_eq!(
1327            getdel(&mut d, b"h", &[b"a", b"nope"]),
1328            [Some("1".to_owned()), None],
1329            "positional, so a field that was not there is a hole and not a gap"
1330        );
1331        assert_eq!(all(&mut d, b"h").len(), 2);
1332        assert_eq!(
1333            getdel(&mut d, b"gone", &[b"a", b"b"]),
1334            [None, None],
1335            "and a missing key is all nils"
1336        );
1337        assert_eq!(d.kind_of(b"gone"), None, "which did not create it");
1338
1339        getdel(&mut d, b"h", &[b"b", b"c"]);
1340        assert_eq!(d.kind_of(b"h"), None, "the last field took the key with it");
1341    }
1342
1343    #[test]
1344    fn getdel_takes_the_deadline_with_the_field() {
1345        let mut d = db();
1346        set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1347        expire(&mut d, b"h", 5_000, &[b"a"]);
1348        assert_eq!(getdel(&mut d, b"h", &[b"a"]), [Some("1".to_owned())]);
1349        set(&mut d, b"h", &[(b"a", b"9")]);
1350        assert_eq!(
1351            ttl_of(&mut d, b"h", &[b"a"]),
1352            [Ask::NoDeadline],
1353            "the field came back without the deadline it had"
1354        );
1355    }
1356
1357    #[test]
1358    fn getex_reads_and_moves_the_deadline_in_one_go() {
1359        let mut d = db();
1360        set(&mut d, b"h", &[(b"a", b"1")]);
1361        assert_eq!(
1362            getex(&mut d, b"h", strings::Expire::Keep, &[b"a"]),
1363            [Some("1".to_owned())]
1364        );
1365        assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1366
1367        getex(&mut d, b"h", strings::Expire::At(5_000), &[b"a"]);
1368        assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(5_000)]);
1369        assert_eq!(
1370            getex(&mut d, b"h", strings::Expire::Keep, &[b"a"]),
1371            [Some("1".to_owned())],
1372            "and a plain read is Keep and not Clear, which is the one place this disagrees with SET"
1373        );
1374        assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(5_000)]);
1375
1376        getex(&mut d, b"h", strings::Expire::Clear, &[b"a"]);
1377        assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1378    }
1379
1380    #[test]
1381    fn getex_hands_back_the_value_of_a_field_it_is_about_to_expire() {
1382        let mut d = db();
1383        set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1384        assert_eq!(
1385            getex(&mut d, b"h", strings::Expire::At(1), &[b"a"]),
1386            [Some("1".to_owned())],
1387            "the read happened before the deadline was applied"
1388        );
1389        assert_eq!(get(&mut d, b"h", b"a"), None);
1390        assert_eq!(d.hlen(b"h").expect("ok"), 1);
1391
1392        getex(&mut d, b"h", strings::Expire::At(1), &[b"b"]);
1393        assert_eq!(d.kind_of(b"h"), None, "and the last one took the key");
1394    }
1395
1396    #[test]
1397    fn setex_writes_all_of_it_or_none_of_it() {
1398        let mut d = db();
1399        assert!(setex(
1400            &mut d,
1401            b"h",
1402            strings::Exists::Always,
1403            strings::Expire::Clear,
1404            &[(b"a", b"1")]
1405        ));
1406        assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1407
1408        assert!(
1409            !setex(
1410                &mut d,
1411                b"h",
1412                strings::Exists::IfMissing,
1413                strings::Expire::Clear,
1414                &[(b"a", b"9"), (b"new", b"9")]
1415            ),
1416            "FNX wants every field named to be missing, and a is not"
1417        );
1418        assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1419        assert_eq!(
1420            get(&mut d, b"h", b"new"),
1421            None,
1422            "and none of it was written"
1423        );
1424
1425        assert!(
1426            !setex(
1427                &mut d,
1428                b"h",
1429                strings::Exists::IfPresent,
1430                strings::Expire::Clear,
1431                &[(b"a", b"9"), (b"nope", b"9")]
1432            ),
1433            "and FXX wants every one of them to be there"
1434        );
1435        assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1436
1437        assert!(setex(
1438            &mut d,
1439            b"h",
1440            strings::Exists::IfPresent,
1441            strings::Expire::Clear,
1442            &[(b"a", b"9")]
1443        ));
1444        assert_eq!(get(&mut d, b"h", b"a"), Some("9".to_owned()));
1445    }
1446
1447    #[test]
1448    fn setex_on_a_key_that_is_not_there_makes_it_only_when_it_can() {
1449        let mut d = db();
1450        assert!(
1451            !setex(
1452                &mut d,
1453                b"gone",
1454                strings::Exists::IfPresent,
1455                strings::Expire::Clear,
1456                &[(b"a", b"1")]
1457            ),
1458            "FXX cannot be met by a key with no fields at all"
1459        );
1460        assert_eq!(d.kind_of(b"gone"), None, "and it was not created");
1461
1462        assert!(setex(
1463            &mut d,
1464            b"fresh",
1465            strings::Exists::IfMissing,
1466            strings::Expire::Clear,
1467            &[(b"a", b"1")]
1468        ));
1469        assert_eq!(get(&mut d, b"fresh", b"a"), Some("1".to_owned()));
1470    }
1471
1472    #[test]
1473    fn setex_keeps_the_deadline_only_when_it_is_asked_to() {
1474        let mut d = db();
1475        set(&mut d, b"h", &[(b"a", b"1")]);
1476        expire(&mut d, b"h", 5_000, &[b"a"]);
1477
1478        setex(
1479            &mut d,
1480            b"h",
1481            strings::Exists::Always,
1482            strings::Expire::Keep,
1483            &[(b"a", b"2")],
1484        );
1485        assert_eq!(get(&mut d, b"h", b"a"), Some("2".to_owned()));
1486        assert_eq!(
1487            ttl_of(&mut d, b"h", &[b"a"]),
1488            [Ask::At(5_000)],
1489            "KEEPTTL put back what the write cleared"
1490        );
1491
1492        setex(
1493            &mut d,
1494            b"h",
1495            strings::Exists::Always,
1496            strings::Expire::Clear,
1497            &[(b"a", b"3")],
1498        );
1499        assert_eq!(
1500            ttl_of(&mut d, b"h", &[b"a"]),
1501            [Ask::NoDeadline],
1502            "and without it the write clears the deadline the way HSET does"
1503        );
1504
1505        setex(
1506            &mut d,
1507            b"h",
1508            strings::Exists::Always,
1509            strings::Expire::At(9_000),
1510            &[(b"a", b"4")],
1511        );
1512        assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(9_000)]);
1513    }
1514
1515    #[test]
1516    fn setex_with_a_deadline_that_has_gone_stores_and_then_removes() {
1517        let mut d = db();
1518        assert!(
1519            setex(
1520                &mut d,
1521                b"h",
1522                strings::Exists::Always,
1523                strings::Expire::At(1),
1524                &[(b"a", b"1")]
1525            ),
1526            "written, and not the separate code the HEXPIRE family has for this"
1527        );
1528        assert_eq!(
1529            d.kind_of(b"h"),
1530            None,
1531            "so a key that did not exist is still not there"
1532        );
1533
1534        set(&mut d, b"h", &[(b"keeper", b"1")]);
1535        setex(
1536            &mut d,
1537            b"h",
1538            strings::Exists::Always,
1539            strings::Expire::At(1),
1540            &[(b"a", b"1")],
1541        );
1542        assert_eq!(d.hlen(b"h").expect("ok"), 1, "and the rest of it survives");
1543    }
1544
1545    #[test]
1546    fn setex_refuses_a_deadline_past_the_ceiling_before_writing_anything() {
1547        let mut d = db();
1548        set(&mut d, b"h", &[(b"a", b"1")]);
1549        let err = d
1550            .hsetex(
1551                b"h",
1552                strings::Exists::Always,
1553                strings::Expire::At(crate::ttl::MAX_AT + 1),
1554                [(b"a".as_slice(), b"2".as_slice())].into_iter(),
1555            )
1556            .expect_err("past the ceiling");
1557        assert_eq!(err.code(), Code::Invalid);
1558        assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1559    }
1560
1561    #[test]
1562    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
1563        let mut d = db();
1564        d.set_plain(b"s", b"v").expect("room");
1565        assert!(
1566            d.hgetdel(b"s", [b"a".as_slice()].into_iter(), |_| {})
1567                .is_err()
1568        );
1569        assert!(
1570            d.hgetex(
1571                b"s",
1572                strings::Expire::Keep,
1573                [b"a".as_slice()].into_iter(),
1574                |_| {}
1575            )
1576            .is_err()
1577        );
1578        assert!(
1579            d.hsetex(
1580                b"s",
1581                strings::Exists::Always,
1582                strings::Expire::Clear,
1583                [(b"a".as_slice(), b"1".as_slice())].into_iter(),
1584            )
1585            .is_err()
1586        );
1587        assert_eq!(d.kind_of(b"s"), Some(Kind::String));
1588    }
1589
1590    #[test]
1591    fn the_last_three_reach_a_table_the_same_way_they_reach_a_listpack() {
1592        let mut d = db();
1593        for i in 0..600u32 {
1594            set(&mut d, b"h", &[(format!("f{i}").as_bytes(), b"v")]);
1595        }
1596        assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1597
1598        setex(
1599            &mut d,
1600            b"h",
1601            strings::Exists::Always,
1602            strings::Expire::At(5_000),
1603            &[(b"f0", b"x")],
1604        );
1605        assert_eq!(ttl_of(&mut d, b"h", &[b"f0"]), [Ask::At(5_000)]);
1606        assert_eq!(
1607            getex(&mut d, b"h", strings::Expire::Clear, &[b"f0"]),
1608            [Some("x".to_owned())]
1609        );
1610        assert_eq!(ttl_of(&mut d, b"h", &[b"f0"]), [Ask::NoDeadline]);
1611        assert_eq!(getdel(&mut d, b"h", &[b"f0"]), [Some("x".to_owned())]);
1612        assert_eq!(d.hlen(b"h").expect("ok"), 599);
1613    }
1614
1615    #[test]
1616    fn a_flush_takes_the_hashes_with_it() {
1617        let mut d = db();
1618        for i in 0..200u32 {
1619            let f = format!("field-{i}");
1620            set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1621        }
1622        set(&mut d, b"other", &[(b"f", b"v")]);
1623        d.clear();
1624
1625        assert_eq!(d.len(), 0);
1626        assert_eq!(d.kind_of(b"h"), None);
1627        // Writing again reuses the slab from the start rather than growing past
1628        // the slots the cleared hashes had.
1629        set(&mut d, b"h", &[(b"f", b"v")]);
1630        assert_eq!(d.hlen(b"h").expect("ok"), 1);
1631    }
1632}