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 hreplace<'a>(
115 &mut self,
116 key: &[u8],
117 pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
118 ) -> Result<()> {
119 for (f, v) in pairs.clone() {
120 strings::check_len(key, f.len())?;
121 strings::check_len(key, v.len())?;
122 }
123 self.hlen(key)?;
124 self.del(key);
125 self.hset(key, pairs)?;
126 Ok(())
127 }
128
129 pub fn hsetnx(&mut self, key: &[u8], field: &[u8], value: &[u8]) -> Result<bool> {
134 strings::check_len(key, field.len())?;
135 strings::check_len(key, value.len())?;
136 let at = match self.hash_slot(key)? {
137 Some(at) => {
138 if self.hash_at(at).contains(field) {
139 return Ok(false);
140 }
141 at
142 }
143 None => self.new_hash(key, 1),
144 };
145 let limits = self.hash_limits;
146 self.hashes
147 .get_mut(at)
148 .expect("the record points at its body")
149 .set(field, value, &limits);
150 Ok(true)
151 }
152
153 pub fn hget<R>(
159 &mut self,
160 key: &[u8],
161 field: &[u8],
162 f: impl FnOnce(Option<Text<'_>>) -> R,
163 ) -> Result<R> {
164 let Some(at) = self.hash_slot(key)? else {
165 return Ok(f(None));
166 };
167 Ok(f(self.hash_at(at).get(field)))
168 }
169
170 pub fn hmget<'a, F>(
177 &mut self,
178 key: &[u8],
179 fields: impl Iterator<Item = &'a [u8]>,
180 mut f: F,
181 ) -> Result<()>
182 where
183 F: FnMut(Option<Text<'_>>),
184 {
185 let slot = self.hash_slot(key)?;
186 for field in fields {
187 match slot {
188 Some(at) => f(self.hash_at(at).get(field)),
189 None => f(None),
190 }
191 }
192 Ok(())
193 }
194
195 pub fn hdel<'a>(
199 &mut self,
200 key: &[u8],
201 fields: impl Iterator<Item = &'a [u8]>,
202 ) -> Result<usize> {
203 let Some(at) = self.hash_slot(key)? else {
204 return Ok(0);
205 };
206 let hash = self
207 .hashes
208 .get_mut(at)
209 .expect("the record points at its body");
210 let mut gone = 0;
211 for field in fields {
212 if hash.remove(field) {
213 gone += 1;
214 }
215 }
216 if hash.is_empty() {
217 self.drop_key(key);
218 }
219 Ok(gone)
220 }
221
222 pub fn hexpire<'a, F>(
238 &mut self,
239 key: &[u8],
240 at: u64,
241 cond: Cond,
242 fields: impl Iterator<Item = &'a [u8]>,
243 mut f: F,
244 ) -> Result<()>
245 where
246 F: FnMut(Applied),
247 {
248 if !ttl::valid_at(at) {
249 return Err(Error::new(Code::Invalid, BAD_EXPIRE));
250 }
251 let Some(slot) = self.hash_slot(key)? else {
252 for _ in fields {
253 f(Applied::Missing);
254 }
255 return Ok(());
256 };
257 let now = self.clock.now_ms();
258 let mut emptied = false;
259 for field in fields {
260 let hash = self.hash_at_mut(slot);
261 let applied = hash.expire(field, at, cond, now);
262 emptied = hash.is_empty();
263 f(applied);
264 }
265 if emptied {
266 self.drop_key(key);
267 }
268 Ok(())
269 }
270
271 pub fn httl<'a, F>(
278 &mut self,
279 key: &[u8],
280 fields: impl Iterator<Item = &'a [u8]>,
281 mut f: F,
282 ) -> Result<()>
283 where
284 F: FnMut(Ask),
285 {
286 let slot = self.hash_slot(key)?;
287 for field in fields {
288 match slot {
289 Some(at) => f(self.hash_at(at).deadline(field)),
290 None => f(Ask::Missing),
291 }
292 }
293 Ok(())
294 }
295
296 pub fn hpersist<'a, F>(
301 &mut self,
302 key: &[u8],
303 fields: impl Iterator<Item = &'a [u8]>,
304 mut f: F,
305 ) -> Result<()>
306 where
307 F: FnMut(Ask),
308 {
309 let slot = self.hash_slot(key)?;
310 for field in fields {
311 match slot {
312 Some(at) => f(self.hash_at_mut(at).persist(field)),
313 None => f(Ask::Missing),
314 }
315 }
316 Ok(())
317 }
318
319 pub fn hgetdel<'a, F>(
329 &mut self,
330 key: &[u8],
331 fields: impl Iterator<Item = &'a [u8]>,
332 mut f: F,
333 ) -> Result<()>
334 where
335 F: FnMut(Option<Text<'_>>),
336 {
337 let Some(slot) = self.hash_slot(key)? else {
338 for _ in fields {
339 f(None);
340 }
341 return Ok(());
342 };
343 for field in fields {
344 let hash = self.hash_at_mut(slot);
345 f(hash.get(field));
346 hash.remove(field);
347 }
348 if self.hash_at(slot).is_empty() {
349 self.drop_key(key);
350 }
351 Ok(())
352 }
353
354 pub fn hgetex<'a, F>(
369 &mut self,
370 key: &[u8],
371 expire: strings::Expire,
372 fields: impl Iterator<Item = &'a [u8]>,
373 mut f: F,
374 ) -> Result<()>
375 where
376 F: FnMut(Option<Text<'_>>),
377 {
378 check_at(expire)?;
381 let Some(slot) = self.hash_slot(key)? else {
382 for _ in fields {
383 f(None);
384 }
385 return Ok(());
386 };
387 let now = self.clock.now_ms();
388 for field in fields {
389 let hash = self.hash_at_mut(slot);
390 f(hash.get(field));
391 match expire {
392 strings::Expire::Keep => {}
393 strings::Expire::Clear => {
394 hash.persist(field);
395 }
396 strings::Expire::At(at) => {
401 hash.expire(field, at, Cond::Always, now);
402 }
403 }
404 }
405 if self.hash_at(slot).is_empty() {
406 self.drop_key(key);
407 }
408 Ok(())
409 }
410
411 pub fn hsetex<'a>(
428 &mut self,
429 key: &[u8],
430 exists: strings::Exists,
431 expire: strings::Expire,
432 pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
433 ) -> Result<bool> {
434 for (f, v) in pairs.clone() {
435 strings::check_len(key, f.len())?;
436 strings::check_len(key, v.len())?;
437 }
438 check_at(expire)?;
439
440 let slot = self.hash_slot(key)?;
441 let met = match exists {
445 strings::Exists::Always => true,
446 strings::Exists::IfMissing => {
447 slot.is_none_or(|at| pairs.clone().all(|(f, _)| !self.hash_at(at).contains(f)))
448 }
449 strings::Exists::IfPresent => {
450 slot.is_some_and(|at| pairs.clone().all(|(f, _)| self.hash_at(at).contains(f)))
451 }
452 };
453 if !met {
454 return Ok(false);
455 }
456 let slot = match slot {
457 Some(at) => at,
458 None => {
459 if pairs.clone().next().is_none() {
460 return Ok(false);
461 }
462 self.new_hash(key, pairs.clone().count())
463 }
464 };
465
466 let limits = self.hash_limits;
467 let now = self.clock.now_ms();
468 for (field, value) in pairs {
469 let hash = self.hash_at_mut(slot);
470 let kept = match expire {
475 strings::Expire::Keep => hash.deadline(field),
476 _ => Ask::Missing,
477 };
478 hash.set(field, value, &limits);
479 match expire {
480 strings::Expire::Clear => {}
481 strings::Expire::Keep => {
482 if let Ask::At(at) = kept {
483 hash.expire(field, at, Cond::Always, now);
484 }
485 }
486 strings::Expire::At(at) => {
487 hash.expire(field, at, Cond::Always, now);
488 }
489 }
490 }
491 if self.hash_at(slot).is_empty() {
492 self.drop_key(key);
493 }
494 Ok(true)
495 }
496
497 pub fn hlen(&mut self, key: &[u8]) -> Result<usize> {
499 match self.hash_slot(key)? {
500 Some(at) => Ok(self.hash_at(at).len()),
501 None => Ok(0),
502 }
503 }
504
505 pub fn hexists(&mut self, key: &[u8], field: &[u8]) -> Result<bool> {
507 match self.hash_slot(key)? {
508 Some(at) => Ok(self.hash_at(at).contains(field)),
509 None => Ok(false),
510 }
511 }
512
513 pub fn hstrlen(&mut self, key: &[u8], field: &[u8]) -> Result<usize> {
518 match self.hash_slot(key)? {
519 Some(at) => Ok(self.hash_at(at).value_len(field).unwrap_or(0)),
520 None => Ok(0),
521 }
522 }
523
524 pub fn hgetall<F>(&mut self, key: &[u8], mut f: F) -> Result<bool>
535 where
536 F: FnMut(Text<'_>, Text<'_>),
537 {
538 self.with_hash(key, |hash| match hash {
539 Some(h) => {
540 for (field, value) in h.iter() {
541 f(field, value);
542 }
543 true
544 }
545 None => false,
546 })
547 }
548
549 pub fn with_hash<R>(&mut self, key: &[u8], f: impl FnOnce(Option<&Hash>) -> R) -> Result<R> {
560 let at = self.hash_slot(key)?;
561 Ok(f(at.map(|at| self.hash_at(at))))
562 }
563
564 pub fn hscan<F>(&mut self, key: &[u8], cursor: Cursor, count: usize, f: F) -> Result<Cursor>
569 where
570 F: FnMut(Text<'_>, Text<'_>),
571 {
572 let Some(at) = self.hash_slot(key)? else {
573 return Ok(Cursor::END);
574 };
575 Ok(self.hash_at(at).scan(cursor, count, f))
576 }
577
578 pub fn hincrby(&mut self, key: &[u8], field: &[u8], by: i64) -> Result<i64> {
586 strings::check_len(key, field.len())?;
587 let at = match self.hash_slot(key)? {
588 Some(at) => at,
589 None => self.new_hash(key, 1),
590 };
591 let current = match self.hash_at(at).get(field) {
592 Some(Text::Int(n)) => n,
593 Some(Text::Str(s)) => {
594 parse_i64(s).ok_or_else(|| Error::new(Code::Invalid, NOT_AN_INT))?
595 }
596 None => 0,
597 };
598 let next = current
599 .checked_add(by)
600 .ok_or_else(|| Error::new(Code::Invalid, WOULD_OVERFLOW))?;
601
602 let mut buf = [0u8; yo_common::num::DIGITS_MAX];
603 let text = yo_common::num::i64_digits(&mut buf, next);
604 let limits = self.hash_limits;
605 self.hashes
606 .get_mut(at)
607 .expect("the record points at its body")
608 .set(field, text, &limits);
609 Ok(next)
610 }
611
612 pub fn hincrbyfloat(&mut self, key: &[u8], field: &[u8], by: f64) -> Result<f64> {
620 strings::check_len(key, field.len())?;
621 let at = match self.hash_slot(key)? {
622 Some(at) => at,
623 None => self.new_hash(key, 1),
624 };
625 let current = match self.hash_at(at).get(field) {
626 Some(Text::Int(n)) => n as f64,
627 Some(Text::Str(s)) => {
628 parse_f64(s).ok_or_else(|| Error::new(Code::Invalid, NOT_A_FLOAT))?
629 }
630 None => 0.0,
631 };
632 let next = current + by;
633 if !next.is_finite() {
634 return Err(Error::new(
635 Code::Invalid,
636 "increment would produce NaN or Infinity",
637 ));
638 }
639
640 let mut buf = [0u8; yo_common::num::DOUBLE_MAX];
641 let text = yo_common::num::write_double(&mut buf, next);
642 let limits = self.hash_limits;
643 self.hashes
644 .get_mut(at)
645 .expect("the record points at its body")
646 .set(field, text, &limits);
647 Ok(next)
648 }
649
650 pub fn hrandfield<R>(
655 &mut self,
656 key: &[u8],
657 f: impl FnOnce(Option<(Text<'_>, Text<'_>)>) -> R,
658 ) -> Result<R> {
659 let Some(at) = self.hash_slot(key)? else {
660 return Ok(f(None));
661 };
662 let pick = self.rng.below(self.hash_at(at).len());
663 Ok(f(self.hash_at(at).at(pick)))
664 }
665
666 pub fn hrandfield_n<F>(&mut self, key: &[u8], count: i64, mut f: F) -> Result<()>
686 where
687 F: FnMut(Text<'_>, Text<'_>),
688 {
689 let Some(at) = self.hash_slot(key)? else {
690 return Ok(());
691 };
692 let rng = &mut self.rng;
696 let hash = self.hashes.get(at).expect("the record points at its body");
697 let len = hash.len();
698
699 let Ok(want) = usize::try_from(count) else {
700 let repeats = usize::try_from(count.unsigned_abs()).unwrap_or(usize::MAX);
701 for _ in 0..repeats {
702 let (field, value) = hash
703 .at(rng.below(len))
704 .expect("the draw was under the length");
705 f(field, value);
706 }
707 return Ok(());
708 };
709
710 let mut left = want.min(len);
711 let mut seen = len;
712 for i in 0..len {
713 if left == 0 {
714 break;
715 }
716 if rng.below(seen) < left {
719 let (field, value) = hash.at(i).expect("i is under the length");
720 f(field, value);
721 left -= 1;
722 }
723 seen -= 1;
724 }
725 Ok(())
726 }
727
728 fn hash_slot(&mut self, key: &[u8]) -> Result<Option<u32>> {
735 let Some(at) = self.live_slot(key, Kind::Hash)? else {
736 return Ok(None);
737 };
738 let now = self.clock.now_ms();
743 let hash = self
744 .hashes
745 .get_mut(at)
746 .expect("the record points at its body");
747 if hash.reap(now) > 0 && hash.is_empty() {
748 self.drop_key(key);
751 return Ok(None);
752 }
753 Ok(Some(at))
754 }
755
756 #[inline]
758 fn hash_at_mut(&mut self, at: u32) -> &mut Hash {
759 self.hashes
760 .get_mut(at)
761 .expect("the record points at its body")
762 }
763
764 #[inline]
770 fn hash_at(&self, at: u32) -> &Hash {
771 self.hashes.get(at).expect("the record points at its body")
772 }
773
774 fn new_hash(&mut self, key: &[u8], hint: usize) -> u32 {
780 let at =
784 yo_alloc::first_touch(|| self.hashes.insert(Hash::with_hint(hint, &self.hash_limits)));
785 let len = value::slot_record_len(false);
786 self.write_rec(key, len, |out| {
787 value::write_slot_record(out, Kind::Hash, at, None);
788 });
789 self.bodies += 1;
790 at
791 }
792}
793
794fn check_at(expire: strings::Expire) -> Result<()> {
801 match expire {
802 strings::Expire::At(at) if !ttl::valid_at(at) => Err(Error::new(Code::Invalid, BAD_EXPIRE)),
803 _ => Ok(()),
804 }
805}
806
807#[cfg(test)]
808mod tests {
809 use super::*;
810 use crate::Clock;
811 use crate::hash::Encoding;
812
813 fn db() -> Keyspace {
814 Keyspace::with_clock(Clock::fixed(1_000))
815 }
816
817 fn set(d: &mut Keyspace, key: &[u8], pairs: &[(&[u8], &[u8])]) -> usize {
818 d.hset(key, pairs.iter().copied()).expect("a hash")
819 }
820
821 fn get(d: &mut Keyspace, key: &[u8], field: &[u8]) -> Option<String> {
822 d.hget(key, field, |t| t.map(|t| text(&t))).expect("a hash")
823 }
824
825 fn text(t: &Text<'_>) -> String {
826 String::from_utf8(t.to_vec()).expect("utf8 in these tests")
827 }
828
829 fn all(d: &mut Keyspace, key: &[u8]) -> Vec<(String, String)> {
830 let mut out = Vec::new();
831 d.hgetall(key, |f, v| out.push((text(&f), text(&v))))
832 .expect("a hash");
833 out.sort();
834 out
835 }
836
837 fn expire(d: &mut Keyspace, key: &[u8], at: u64, fields: &[&[u8]]) -> Vec<Applied> {
838 let mut out = Vec::new();
839 d.hexpire(key, at, Cond::Always, fields.iter().copied(), |a| {
840 out.push(a);
841 })
842 .expect("a hash");
843 out
844 }
845
846 fn ttl_of(d: &mut Keyspace, key: &[u8], fields: &[&[u8]]) -> Vec<Ask> {
847 let mut out = Vec::new();
848 d.httl(key, fields.iter().copied(), |a| out.push(a))
849 .expect("a hash");
850 out
851 }
852
853 #[test]
854 fn setting_a_field_on_a_key_that_is_not_there_makes_it() {
855 let mut d = db();
856 assert_eq!(set(&mut d, b"h", &[(b"f", b"v")]), 1);
857 assert_eq!(d.kind_of(b"h"), Some(Kind::Hash));
858 assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("v"));
859 }
860
861 #[test]
862 fn writing_a_field_again_is_not_a_new_field() {
863 let mut d = db();
864 assert_eq!(set(&mut d, b"h", &[(b"f", b"one"), (b"g", b"two")]), 2);
865 assert_eq!(set(&mut d, b"h", &[(b"f", b"three")]), 0, "f was there");
866 assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("three"));
867 assert_eq!(d.hlen(b"h").expect("a hash"), 2);
868 }
869
870 #[test]
871 fn an_empty_write_does_not_make_a_key() {
872 let mut d = db();
873 let none: [(&[u8], &[u8]); 0] = [];
874 assert_eq!(d.hset(b"h", none.iter().copied()).expect("ok"), 0);
875 assert_eq!(d.kind_of(b"h"), None, "an empty hash does not exist");
876 }
877
878 #[test]
879 fn losing_the_last_field_loses_the_key() {
880 let mut d = db();
881 set(&mut d, b"h", &[(b"f", b"v"), (b"g", b"w")]);
882 assert_eq!(d.hdel(b"h", [b"f".as_slice()].into_iter()).expect("ok"), 1);
883 assert_eq!(d.kind_of(b"h"), Some(Kind::Hash), "g is still there");
884 assert_eq!(d.hdel(b"h", [b"g".as_slice()].into_iter()).expect("ok"), 1);
885 assert_eq!(d.kind_of(b"h"), None, "and now nothing is");
886 assert_eq!(d.len(), 0);
887 }
888
889 #[test]
890 fn every_command_says_wrongtype_for_a_string() {
891 let mut d = db();
892 d.set_plain(b"s", b"v").expect("room");
893
894 assert_eq!(
895 d.hset(b"s", [(b"f".as_slice(), b"v".as_slice())].into_iter())
896 .unwrap_err()
897 .code(),
898 Code::WrongType
899 );
900 assert!(d.hget(b"s", b"f", |_| ()).is_err());
901 assert!(d.hdel(b"s", [b"f".as_slice()].into_iter()).is_err());
902 assert!(d.hlen(b"s").is_err());
903 assert!(d.hexists(b"s", b"f").is_err());
904 assert!(d.hstrlen(b"s", b"f").is_err());
905 assert!(d.hgetall(b"s", |_, _| ()).is_err());
906 assert!(d.hsetnx(b"s", b"f", b"v").is_err());
907 assert!(d.hincrby(b"s", b"f", 1).is_err());
908 assert!(d.hincrbyfloat(b"s", b"f", 1.0).is_err());
909 assert!(d.hrandfield(b"s", |_| ()).is_err());
910 assert!(d.hrandfield_n(b"s", 1, |_, _| ()).is_err());
911 assert!(d.hscan(b"s", Cursor::START, 10, |_, _| ()).is_err());
912 assert!(
913 d.hmget(b"s", [b"f".as_slice()].into_iter(), |_| ())
914 .is_err()
915 );
916
917 assert_eq!(
918 d.kind_of(b"s"),
919 Some(Kind::String),
920 "and none of them wrote anything"
921 );
922 }
923
924 #[test]
925 fn a_missing_key_reads_as_an_empty_hash() {
926 let mut d = db();
927 assert_eq!(d.hlen(b"nope").expect("ok"), 0);
928 assert!(!d.hexists(b"nope", b"f").expect("ok"));
929 assert_eq!(d.hstrlen(b"nope", b"f").expect("ok"), 0);
930 assert_eq!(get(&mut d, b"nope", b"f"), None);
931 assert!(!d.hgetall(b"nope", |_, _| ()).expect("ok"));
932 assert_eq!(
933 d.hdel(b"nope", [b"f".as_slice()].into_iter()).expect("ok"),
934 0
935 );
936 }
937
938 #[test]
939 fn hmget_answers_once_per_field_asked_for() {
940 let mut d = db();
941 set(&mut d, b"h", &[(b"a", b"1"), (b"c", b"3")]);
942
943 let mut got = Vec::new();
944 d.hmget(b"h", [b"a".as_slice(), b"b", b"c"].into_iter(), |t| {
945 got.push(t.map(|t| text(&t)));
946 })
947 .expect("a hash");
948 assert_eq!(
949 got,
950 vec![Some("1".into()), None, Some("3".into())],
951 "the reply is positional, so b gets a nil and not a gap"
952 );
953
954 let mut missing = Vec::new();
955 d.hmget(b"gone", [b"a".as_slice(), b"b"].into_iter(), |t| {
956 missing.push(t.is_none());
957 })
958 .expect("no key");
959 assert_eq!(missing, vec![true, true], "a missing key is all nils");
960 }
961
962 #[test]
963 fn hsetnx_writes_only_a_field_that_is_not_there() {
964 let mut d = db();
965 assert!(d.hsetnx(b"h", b"f", b"one").expect("ok"), "made the key");
966 assert!(!d.hsetnx(b"h", b"f", b"two").expect("ok"), "f was there");
967 assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("one"));
968 assert!(
969 d.hsetnx(b"h", b"g", b"two").expect("ok"),
970 "and it is per field, not per key"
971 );
972 assert_eq!(d.hlen(b"h").expect("ok"), 2);
973 }
974
975 #[test]
976 fn hstrlen_counts_a_number_without_writing_it() {
977 let mut d = db();
978 set(&mut d, b"h", &[(b"n", b"-12345"), (b"s", b"hello")]);
979 assert_eq!(d.hstrlen(b"h", b"n").expect("ok"), 6);
980 assert_eq!(d.hstrlen(b"h", b"s").expect("ok"), 5);
981 assert_eq!(d.hstrlen(b"h", b"nope").expect("ok"), 0);
982 }
983
984 #[test]
985 fn incrementing_counts_up_from_nothing_and_refuses_what_is_not_a_number() {
986 let mut d = db();
987 assert_eq!(d.hincrby(b"h", b"n", 5).expect("ok"), 5, "absent is zero");
988 assert_eq!(d.hincrby(b"h", b"n", -7).expect("ok"), -2);
989 assert_eq!(get(&mut d, b"h", b"n").as_deref(), Some("-2"));
990
991 set(&mut d, b"h", &[(b"s", b"words")]);
992 let err = d.hincrby(b"h", b"s", 1).unwrap_err();
993 assert_eq!(err.code(), Code::Invalid);
994 assert_eq!(err.message(), NOT_AN_INT);
995 assert_eq!(
996 get(&mut d, b"h", b"s").as_deref(),
997 Some("words"),
998 "and it left the field alone"
999 );
1000 }
1001
1002 #[test]
1003 fn an_increment_that_leaves_the_range_is_refused_and_not_wrapped() {
1004 let mut d = db();
1005 let max = i64::MAX.to_string();
1006 set(&mut d, b"h", &[(b"n", max.as_bytes())]);
1007 let err = d.hincrby(b"h", b"n", 1).unwrap_err();
1008 assert_eq!(err.message(), WOULD_OVERFLOW);
1009 assert_eq!(
1010 get(&mut d, b"h", b"n").as_deref(),
1011 Some(max.as_str()),
1012 "the field still holds what it held"
1013 );
1014 }
1015
1016 #[test]
1017 fn incrementing_by_a_float_reports_the_sum_and_refuses_infinity() {
1018 let mut d = db();
1019 assert!((d.hincrbyfloat(b"h", b"f", 10.5).expect("ok") - 10.5).abs() < 1e-9);
1020 assert!((d.hincrbyfloat(b"h", b"f", 0.1).expect("ok") - 10.6).abs() < 1e-9);
1021
1022 let err = d.hincrbyfloat(b"h", b"f", f64::INFINITY).unwrap_err();
1023 assert_eq!(err.message(), "increment would produce NaN or Infinity");
1024
1025 set(&mut d, b"h", &[(b"s", b"words")]);
1026 assert_eq!(
1027 d.hincrbyfloat(b"h", b"s", 1.0).unwrap_err().message(),
1028 NOT_A_FLOAT
1029 );
1030 }
1031
1032 #[test]
1033 fn a_hash_promotes_in_the_keyspace_and_object_encoding_says_so() {
1034 let mut d = db();
1035 set(&mut d, b"h", &[(b"f", b"v")]);
1036 assert_eq!(d.hash_encoding(b"h"), Some(Encoding::Listpack));
1037 assert_eq!(d.encoding_name(b"h"), Some("listpack"));
1038
1039 for i in 0..600u32 {
1040 let f = format!("field-{i}");
1041 set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1042 }
1043 assert_eq!(d.hash_encoding(b"h"), Some(Encoding::Hashtable));
1044 assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1045 assert_eq!(d.hlen(b"h").expect("ok"), 601);
1046 assert_eq!(
1047 d.hash_encoding(b"missing"),
1048 None,
1049 "and a key that is not a hash has no hash encoding"
1050 );
1051 }
1052
1053 #[test]
1054 fn a_hash_survives_being_given_a_deadline_and_goes_when_it_passes() {
1055 let mut d = db();
1056 set(&mut d, b"h", &[(b"f", b"v"), (b"g", b"w")]);
1057 assert!(d.set_expiry(b"h", Some(1_100)));
1058 assert_eq!(
1059 all(&mut d, b"h"),
1060 vec![("f".into(), "v".into()), ("g".into(), "w".into())],
1061 "writing the record did not touch the body"
1062 );
1063
1064 d.clock().advance(100);
1065 assert_eq!(d.kind_of(b"h"), None);
1066 assert_eq!(d.len(), 0);
1067 assert_eq!(d.expired_keys(), 1);
1068 }
1069
1070 #[test]
1071 fn writing_a_string_over_a_hash_gives_the_body_back() {
1072 let mut d = db();
1073 for i in 0..300u32 {
1074 let f = format!("field-{i}");
1075 set(&mut d, b"h", &[(f.as_bytes(), b"a value of some length")]);
1076 }
1077 assert_eq!(d.hashes.len(), 1);
1078 let held = d.memory_bytes();
1079 d.set_plain(b"h", b"now a string").expect("room");
1080
1081 assert_eq!(d.kind_of(b"h"), Some(Kind::String));
1082 assert_eq!(d.hashes.len(), 0, "the body went with the record");
1087 assert!(d.memory_bytes() < held, "and its bytes went with it");
1088 }
1089
1090 #[test]
1091 fn a_scan_walks_a_hash_in_the_keyspace_exactly_once() {
1092 let mut d = db();
1093 for i in 0..500u32 {
1094 let f = format!("field-{i}");
1095 let v = format!("value-{i}");
1096 set(&mut d, b"h", &[(f.as_bytes(), v.as_bytes())]);
1097 }
1098
1099 let mut seen: Vec<(String, String)> = Vec::new();
1100 let mut cursor = Cursor::START;
1101 loop {
1102 cursor = d
1103 .hscan(b"h", cursor, 32, |f, v| seen.push((text(&f), text(&v))))
1104 .expect("a hash");
1105 if cursor == Cursor::END {
1106 break;
1107 }
1108 }
1109 seen.sort();
1110 seen.dedup();
1111 assert_eq!(seen.len(), 500, "every field once and only once");
1112 for (f, v) in &seen {
1113 assert_eq!(
1114 f.strip_prefix("field-"),
1115 v.strip_prefix("value-"),
1116 "and paired with its own value"
1117 );
1118 }
1119 }
1120
1121 #[test]
1122 fn a_draw_takes_the_count_asked_for_and_repeats_only_when_told_to() {
1123 let mut d = db();
1124 d.seed(7);
1125 for i in 0..10u32 {
1126 let f = format!("f{i}");
1127 set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1128 }
1129
1130 let mut got = Vec::new();
1131 d.hrandfield_n(b"h", 4, |f, _| got.push(text(&f)))
1132 .expect("ok");
1133 assert_eq!(got.len(), 4);
1134 got.sort();
1135 got.dedup();
1136 assert_eq!(got.len(), 4, "a positive count is distinct");
1137
1138 let mut over = Vec::new();
1139 d.hrandfield_n(b"h", 25, |f, _| over.push(text(&f)))
1140 .expect("ok");
1141 assert_eq!(over.len(), 10, "and never more than the hash holds");
1142
1143 let mut with_repeats = Vec::new();
1144 d.hrandfield_n(b"h", -25, |f, _| with_repeats.push(text(&f)))
1145 .expect("ok");
1146 assert_eq!(
1147 with_repeats.len(),
1148 25,
1149 "a negative count is exactly that many, repeats and all"
1150 );
1151
1152 let one = d
1153 .hrandfield(b"h", |p| p.map(|(f, _)| text(&f)))
1154 .expect("ok");
1155 assert!(one.is_some());
1156 assert!(
1157 d.hrandfield(b"gone", |p| p.is_none()).expect("ok"),
1158 "and a missing key draws a nil"
1159 );
1160 }
1161
1162 #[test]
1163 fn a_field_deadline_goes_on_and_is_reported_back() {
1164 let mut d = db();
1165 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1166 assert_eq!(
1167 expire(&mut d, b"h", 5_000, &[b"a", b"nope"]),
1168 [Applied::Ok, Applied::Missing],
1169 "one call per field, in the order asked"
1170 );
1171 assert_eq!(
1172 ttl_of(&mut d, b"h", &[b"a", b"b", b"nope"]),
1173 [Ask::At(5_000), Ask::NoDeadline, Ask::Missing]
1174 );
1175 assert_eq!(
1176 d.encoding_name(b"h"),
1177 Some("listpackex"),
1178 "and the band widened to hold it"
1179 );
1180 }
1181
1182 #[test]
1183 fn a_field_is_gone_the_next_time_the_key_is_touched() {
1184 let mut d = db();
1185 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1186 expire(&mut d, b"h", 2_000, &[b"a"]);
1187
1188 assert_eq!(d.hlen(b"h").expect("ok"), 2, "still there at 1000");
1189 d.clock().advance(1_000);
1190 assert_eq!(d.hlen(b"h").expect("ok"), 1, "and gone at 2000");
1191 assert_eq!(get(&mut d, b"h", b"a"), None);
1192 assert_eq!(get(&mut d, b"h", b"b").as_deref(), Some("2"));
1193 assert_eq!(all(&mut d, b"h"), [("b".to_owned(), "2".to_owned())]);
1194 }
1195
1196 #[test]
1197 fn the_key_goes_when_its_last_field_expires() {
1198 let mut d = db();
1199 set(&mut d, b"h", &[(b"a", b"1")]);
1200 expire(&mut d, b"h", 2_000, &[b"a"]);
1201 assert_eq!(d.kind_of(b"h"), Some(Kind::Hash));
1202
1203 d.clock().advance(1_000);
1204 assert_eq!(d.hlen(b"h").expect("ok"), 0);
1205 assert_eq!(d.kind_of(b"h"), None, "an empty hash is not stored");
1206 assert_eq!(d.len(), 0);
1207 }
1208
1209 #[test]
1212 fn a_deadline_already_past_deletes_the_field_now() {
1213 let mut d = db();
1214 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1215 assert_eq!(expire(&mut d, b"h", 500, &[b"a"]), [Applied::Deleted]);
1216 assert_eq!(d.hlen(b"h").expect("ok"), 1);
1217
1218 assert_eq!(expire(&mut d, b"h", 500, &[b"b"]), [Applied::Deleted]);
1219 assert_eq!(d.kind_of(b"h"), None);
1220 }
1221
1222 #[test]
1223 fn persisting_puts_the_field_back_to_no_deadline() {
1224 let mut d = db();
1225 set(&mut d, b"h", &[(b"a", b"1")]);
1226 expire(&mut d, b"h", 5_000, &[b"a"]);
1227
1228 let mut out = Vec::new();
1229 d.hpersist(
1230 b"h",
1231 [b"a".as_slice(), b"nope".as_slice()].into_iter(),
1232 |a| {
1233 out.push(a);
1234 },
1235 )
1236 .expect("ok");
1237 assert_eq!(out, [Ask::At(5_000), Ask::Missing]);
1238 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1239
1240 d.clock().advance(100_000);
1241 assert_eq!(d.hlen(b"h").expect("ok"), 1, "and it outlives its deadline");
1242 }
1243
1244 #[test]
1245 fn a_missing_key_answers_no_field_for_every_field_it_was_asked() {
1246 let mut d = db();
1247 assert_eq!(
1248 expire(&mut d, b"gone", 5_000, &[b"a", b"b"]),
1249 [Applied::Missing, Applied::Missing]
1250 );
1251 assert_eq!(
1252 ttl_of(&mut d, b"gone", &[b"a", b"b"]),
1253 [Ask::Missing, Ask::Missing]
1254 );
1255 assert_eq!(d.kind_of(b"gone"), None, "and asking did not create it");
1256 }
1257
1258 #[test]
1259 fn a_deadline_past_the_ceiling_is_refused_before_any_field_moves() {
1260 let mut d = db();
1261 set(&mut d, b"h", &[(b"a", b"1")]);
1262 let err = d
1263 .hexpire(
1264 b"h",
1265 crate::ttl::MAX_AT + 1,
1266 Cond::Always,
1267 [b"a".as_slice()].into_iter(),
1268 |_| unreachable!("no field is reached"),
1269 )
1270 .expect_err("past the ceiling");
1271 assert_eq!(err.code(), Code::Invalid);
1272 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1273 }
1274
1275 #[test]
1276 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
1277 let mut d = db();
1278 d.set_plain(b"s", b"v").expect("room");
1279 assert!(
1280 d.hexpire(
1281 b"s",
1282 5_000,
1283 Cond::Always,
1284 [b"a".as_slice()].into_iter(),
1285 |_| { unreachable!("nothing is reached") }
1286 )
1287 .is_err()
1288 );
1289 assert!(d.httl(b"s", [b"a".as_slice()].into_iter(), |_| {}).is_err());
1290 assert!(
1291 d.hpersist(b"s", [b"a".as_slice()].into_iter(), |_| {})
1292 .is_err()
1293 );
1294 assert_eq!(
1295 d.kind_of(b"s"),
1296 Some(Kind::String),
1297 "and the string is intact"
1298 );
1299 }
1300
1301 #[test]
1302 fn a_hash_that_never_expires_a_field_is_untouched_by_all_of_this() {
1303 let mut d = db();
1304 for i in 0..600u32 {
1305 set(&mut d, b"h", &[(format!("f{i}").as_bytes(), b"v")]);
1306 }
1307 assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1308 d.clock().advance(1_000_000);
1309 assert_eq!(d.hlen(b"h").expect("ok"), 600, "nothing had a deadline");
1310 }
1311
1312 fn getdel(d: &mut Keyspace, key: &[u8], fields: &[&[u8]]) -> Vec<Option<String>> {
1314 let mut out = Vec::new();
1315 d.hgetdel(key, fields.iter().copied(), |t| {
1316 out.push(t.map(|t| text(&t)));
1317 })
1318 .expect("a hash");
1319 out
1320 }
1321
1322 fn getex(
1324 d: &mut Keyspace,
1325 key: &[u8],
1326 expire: strings::Expire,
1327 fields: &[&[u8]],
1328 ) -> Vec<Option<String>> {
1329 let mut out = Vec::new();
1330 d.hgetex(key, expire, fields.iter().copied(), |t| {
1331 out.push(t.map(|t| text(&t)));
1332 })
1333 .expect("a hash");
1334 out
1335 }
1336
1337 fn setex(
1339 d: &mut Keyspace,
1340 key: &[u8],
1341 exists: strings::Exists,
1342 expire: strings::Expire,
1343 pairs: &[(&[u8], &[u8])],
1344 ) -> bool {
1345 d.hsetex(key, exists, expire, pairs.iter().copied())
1346 .expect("a hash")
1347 }
1348
1349 #[test]
1350 fn getdel_hands_the_value_back_and_then_takes_the_field() {
1351 let mut d = db();
1352 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")]);
1353 assert_eq!(
1354 getdel(&mut d, b"h", &[b"a", b"nope"]),
1355 [Some("1".to_owned()), None],
1356 "positional, so a field that was not there is a hole and not a gap"
1357 );
1358 assert_eq!(all(&mut d, b"h").len(), 2);
1359 assert_eq!(
1360 getdel(&mut d, b"gone", &[b"a", b"b"]),
1361 [None, None],
1362 "and a missing key is all nils"
1363 );
1364 assert_eq!(d.kind_of(b"gone"), None, "which did not create it");
1365
1366 getdel(&mut d, b"h", &[b"b", b"c"]);
1367 assert_eq!(d.kind_of(b"h"), None, "the last field took the key with it");
1368 }
1369
1370 #[test]
1371 fn getdel_takes_the_deadline_with_the_field() {
1372 let mut d = db();
1373 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1374 expire(&mut d, b"h", 5_000, &[b"a"]);
1375 assert_eq!(getdel(&mut d, b"h", &[b"a"]), [Some("1".to_owned())]);
1376 set(&mut d, b"h", &[(b"a", b"9")]);
1377 assert_eq!(
1378 ttl_of(&mut d, b"h", &[b"a"]),
1379 [Ask::NoDeadline],
1380 "the field came back without the deadline it had"
1381 );
1382 }
1383
1384 #[test]
1385 fn getex_reads_and_moves_the_deadline_in_one_go() {
1386 let mut d = db();
1387 set(&mut d, b"h", &[(b"a", b"1")]);
1388 assert_eq!(
1389 getex(&mut d, b"h", strings::Expire::Keep, &[b"a"]),
1390 [Some("1".to_owned())]
1391 );
1392 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1393
1394 getex(&mut d, b"h", strings::Expire::At(5_000), &[b"a"]);
1395 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(5_000)]);
1396 assert_eq!(
1397 getex(&mut d, b"h", strings::Expire::Keep, &[b"a"]),
1398 [Some("1".to_owned())],
1399 "and a plain read is Keep and not Clear, which is the one place this disagrees with SET"
1400 );
1401 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(5_000)]);
1402
1403 getex(&mut d, b"h", strings::Expire::Clear, &[b"a"]);
1404 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1405 }
1406
1407 #[test]
1408 fn getex_hands_back_the_value_of_a_field_it_is_about_to_expire() {
1409 let mut d = db();
1410 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1411 assert_eq!(
1412 getex(&mut d, b"h", strings::Expire::At(1), &[b"a"]),
1413 [Some("1".to_owned())],
1414 "the read happened before the deadline was applied"
1415 );
1416 assert_eq!(get(&mut d, b"h", b"a"), None);
1417 assert_eq!(d.hlen(b"h").expect("ok"), 1);
1418
1419 getex(&mut d, b"h", strings::Expire::At(1), &[b"b"]);
1420 assert_eq!(d.kind_of(b"h"), None, "and the last one took the key");
1421 }
1422
1423 #[test]
1424 fn setex_writes_all_of_it_or_none_of_it() {
1425 let mut d = db();
1426 assert!(setex(
1427 &mut d,
1428 b"h",
1429 strings::Exists::Always,
1430 strings::Expire::Clear,
1431 &[(b"a", b"1")]
1432 ));
1433 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1434
1435 assert!(
1436 !setex(
1437 &mut d,
1438 b"h",
1439 strings::Exists::IfMissing,
1440 strings::Expire::Clear,
1441 &[(b"a", b"9"), (b"new", b"9")]
1442 ),
1443 "FNX wants every field named to be missing, and a is not"
1444 );
1445 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1446 assert_eq!(
1447 get(&mut d, b"h", b"new"),
1448 None,
1449 "and none of it was written"
1450 );
1451
1452 assert!(
1453 !setex(
1454 &mut d,
1455 b"h",
1456 strings::Exists::IfPresent,
1457 strings::Expire::Clear,
1458 &[(b"a", b"9"), (b"nope", b"9")]
1459 ),
1460 "and FXX wants every one of them to be there"
1461 );
1462 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1463
1464 assert!(setex(
1465 &mut d,
1466 b"h",
1467 strings::Exists::IfPresent,
1468 strings::Expire::Clear,
1469 &[(b"a", b"9")]
1470 ));
1471 assert_eq!(get(&mut d, b"h", b"a"), Some("9".to_owned()));
1472 }
1473
1474 #[test]
1475 fn setex_on_a_key_that_is_not_there_makes_it_only_when_it_can() {
1476 let mut d = db();
1477 assert!(
1478 !setex(
1479 &mut d,
1480 b"gone",
1481 strings::Exists::IfPresent,
1482 strings::Expire::Clear,
1483 &[(b"a", b"1")]
1484 ),
1485 "FXX cannot be met by a key with no fields at all"
1486 );
1487 assert_eq!(d.kind_of(b"gone"), None, "and it was not created");
1488
1489 assert!(setex(
1490 &mut d,
1491 b"fresh",
1492 strings::Exists::IfMissing,
1493 strings::Expire::Clear,
1494 &[(b"a", b"1")]
1495 ));
1496 assert_eq!(get(&mut d, b"fresh", b"a"), Some("1".to_owned()));
1497 }
1498
1499 #[test]
1500 fn setex_keeps_the_deadline_only_when_it_is_asked_to() {
1501 let mut d = db();
1502 set(&mut d, b"h", &[(b"a", b"1")]);
1503 expire(&mut d, b"h", 5_000, &[b"a"]);
1504
1505 setex(
1506 &mut d,
1507 b"h",
1508 strings::Exists::Always,
1509 strings::Expire::Keep,
1510 &[(b"a", b"2")],
1511 );
1512 assert_eq!(get(&mut d, b"h", b"a"), Some("2".to_owned()));
1513 assert_eq!(
1514 ttl_of(&mut d, b"h", &[b"a"]),
1515 [Ask::At(5_000)],
1516 "KEEPTTL put back what the write cleared"
1517 );
1518
1519 setex(
1520 &mut d,
1521 b"h",
1522 strings::Exists::Always,
1523 strings::Expire::Clear,
1524 &[(b"a", b"3")],
1525 );
1526 assert_eq!(
1527 ttl_of(&mut d, b"h", &[b"a"]),
1528 [Ask::NoDeadline],
1529 "and without it the write clears the deadline the way HSET does"
1530 );
1531
1532 setex(
1533 &mut d,
1534 b"h",
1535 strings::Exists::Always,
1536 strings::Expire::At(9_000),
1537 &[(b"a", b"4")],
1538 );
1539 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(9_000)]);
1540 }
1541
1542 #[test]
1543 fn setex_with_a_deadline_that_has_gone_stores_and_then_removes() {
1544 let mut d = db();
1545 assert!(
1546 setex(
1547 &mut d,
1548 b"h",
1549 strings::Exists::Always,
1550 strings::Expire::At(1),
1551 &[(b"a", b"1")]
1552 ),
1553 "written, and not the separate code the HEXPIRE family has for this"
1554 );
1555 assert_eq!(
1556 d.kind_of(b"h"),
1557 None,
1558 "so a key that did not exist is still not there"
1559 );
1560
1561 set(&mut d, b"h", &[(b"keeper", b"1")]);
1562 setex(
1563 &mut d,
1564 b"h",
1565 strings::Exists::Always,
1566 strings::Expire::At(1),
1567 &[(b"a", b"1")],
1568 );
1569 assert_eq!(d.hlen(b"h").expect("ok"), 1, "and the rest of it survives");
1570 }
1571
1572 #[test]
1573 fn setex_refuses_a_deadline_past_the_ceiling_before_writing_anything() {
1574 let mut d = db();
1575 set(&mut d, b"h", &[(b"a", b"1")]);
1576 let err = d
1577 .hsetex(
1578 b"h",
1579 strings::Exists::Always,
1580 strings::Expire::At(crate::ttl::MAX_AT + 1),
1581 [(b"a".as_slice(), b"2".as_slice())].into_iter(),
1582 )
1583 .expect_err("past the ceiling");
1584 assert_eq!(err.code(), Code::Invalid);
1585 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1586 }
1587
1588 #[test]
1589 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
1590 let mut d = db();
1591 d.set_plain(b"s", b"v").expect("room");
1592 assert!(
1593 d.hgetdel(b"s", [b"a".as_slice()].into_iter(), |_| {})
1594 .is_err()
1595 );
1596 assert!(
1597 d.hgetex(
1598 b"s",
1599 strings::Expire::Keep,
1600 [b"a".as_slice()].into_iter(),
1601 |_| {}
1602 )
1603 .is_err()
1604 );
1605 assert!(
1606 d.hsetex(
1607 b"s",
1608 strings::Exists::Always,
1609 strings::Expire::Clear,
1610 [(b"a".as_slice(), b"1".as_slice())].into_iter(),
1611 )
1612 .is_err()
1613 );
1614 assert_eq!(d.kind_of(b"s"), Some(Kind::String));
1615 }
1616
1617 #[test]
1618 fn the_last_three_reach_a_table_the_same_way_they_reach_a_listpack() {
1619 let mut d = db();
1620 for i in 0..600u32 {
1621 set(&mut d, b"h", &[(format!("f{i}").as_bytes(), b"v")]);
1622 }
1623 assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1624
1625 setex(
1626 &mut d,
1627 b"h",
1628 strings::Exists::Always,
1629 strings::Expire::At(5_000),
1630 &[(b"f0", b"x")],
1631 );
1632 assert_eq!(ttl_of(&mut d, b"h", &[b"f0"]), [Ask::At(5_000)]);
1633 assert_eq!(
1634 getex(&mut d, b"h", strings::Expire::Clear, &[b"f0"]),
1635 [Some("x".to_owned())]
1636 );
1637 assert_eq!(ttl_of(&mut d, b"h", &[b"f0"]), [Ask::NoDeadline]);
1638 assert_eq!(getdel(&mut d, b"h", &[b"f0"]), [Some("x".to_owned())]);
1639 assert_eq!(d.hlen(b"h").expect("ok"), 599);
1640 }
1641
1642 #[test]
1643 fn a_flush_takes_the_hashes_with_it() {
1644 let mut d = db();
1645 for i in 0..200u32 {
1646 let f = format!("field-{i}");
1647 set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1648 }
1649 set(&mut d, b"other", &[(b"f", b"v")]);
1650 d.clear();
1651
1652 assert_eq!(d.len(), 0);
1653 assert_eq!(d.kind_of(b"h"), None);
1654 set(&mut d, b"h", &[(b"f", b"v")]);
1657 assert_eq!(d.hlen(b"h").expect("ok"), 1);
1658 }
1659}