1use 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
46const NOT_AN_INT: &str = "hash value is not an integer";
48const NOT_A_FLOAT: &str = "hash value is not a float";
50const WOULD_OVERFLOW: &str = "increment or decrement would overflow";
52const BAD_EXPIRE: &str = "invalid expire time, must be >= 0";
54
55impl Keyspace {
56 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 self.drop_key(key);
724 return Ok(None);
725 }
726 Ok(Some(at))
727 }
728
729 #[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 #[inline]
743 fn hash_at(&self, at: u32) -> &Hash {
744 self.hashes.get(at).expect("the record points at its body")
745 }
746
747 fn new_hash(&mut self, key: &[u8], hint: usize) -> u32 {
753 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
767fn 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 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 #[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 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 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 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 set(&mut d, b"h", &[(b"f", b"v")]);
1630 assert_eq!(d.hlen(b"h").expect("ok"), 1);
1631 }
1632}