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::news;
42use crate::scan::Cursor;
43use crate::strings;
44use crate::ttl::{self, Applied, Ask, Cond};
45use crate::value::{self, Kind};
46
47const NOT_AN_INT: &str = "hash value is not an integer";
49const NOT_A_FLOAT: &str = "hash value is not a float";
51const WOULD_OVERFLOW: &str = "increment or decrement would overflow";
53const BAD_EXPIRE: &str = "invalid expire time, must be >= 0";
55
56impl Keyspace {
57 pub fn hset<'a>(
68 &mut self,
69 key: &[u8],
70 pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
71 ) -> Result<usize> {
72 for (f, v) in pairs.clone() {
73 strings::check_len(key, f.len())?;
74 strings::check_len(key, v.len())?;
75 }
76 let at = match self.hash_slot(key)? {
77 Some(at) => at,
78 None => {
79 if pairs.clone().next().is_none() {
80 return Ok(0);
81 }
82 let hint = pairs.clone().count();
83 self.new_hash(key, hint)
84 }
85 };
86
87 let limits = self.hash_limits;
90 let hash = self
91 .hashes
92 .get_mut(at)
93 .expect("the record points at its body");
94 let mut added = 0;
95 for (field, value) in pairs {
96 if hash.set(field, value, &limits) {
97 added += 1;
98 }
99 }
100 Ok(added)
101 }
102
103 pub fn hreplace<'a>(
116 &mut self,
117 key: &[u8],
118 pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
119 ) -> Result<()> {
120 for (f, v) in pairs.clone() {
121 strings::check_len(key, f.len())?;
122 strings::check_len(key, v.len())?;
123 }
124 self.hlen(key)?;
125 self.del(key);
126 self.hset(key, pairs)?;
127 Ok(())
128 }
129
130 pub fn hsetnx(&mut self, key: &[u8], field: &[u8], value: &[u8]) -> Result<bool> {
135 strings::check_len(key, field.len())?;
136 strings::check_len(key, value.len())?;
137 let at = match self.hash_slot(key)? {
138 Some(at) => {
139 if self.hash_at(at).contains(field) {
140 return Ok(false);
141 }
142 at
143 }
144 None => self.new_hash(key, 1),
145 };
146 let limits = self.hash_limits;
147 self.hashes
148 .get_mut(at)
149 .expect("the record points at its body")
150 .set(field, value, &limits);
151 Ok(true)
152 }
153
154 pub fn hget<R>(
160 &mut self,
161 key: &[u8],
162 field: &[u8],
163 f: impl FnOnce(Option<Text<'_>>) -> R,
164 ) -> Result<R> {
165 let Some(at) = self.hash_slot(key)? else {
166 return Ok(f(None));
167 };
168 Ok(f(self.hash_at(at).get(field)))
169 }
170
171 pub fn hmget<'a, F>(
178 &mut self,
179 key: &[u8],
180 fields: impl Iterator<Item = &'a [u8]>,
181 mut f: F,
182 ) -> Result<()>
183 where
184 F: FnMut(Option<Text<'_>>),
185 {
186 let slot = self.hash_slot(key)?;
187 for field in fields {
188 match slot {
189 Some(at) => f(self.hash_at(at).get(field)),
190 None => f(None),
191 }
192 }
193 Ok(())
194 }
195
196 pub fn hdel<'a>(
200 &mut self,
201 key: &[u8],
202 fields: impl Iterator<Item = &'a [u8]>,
203 ) -> Result<usize> {
204 self.hdel_each(key, fields, |_| {})
205 }
206
207 pub fn hdel_each<'a>(
215 &mut self,
216 key: &[u8],
217 fields: impl Iterator<Item = &'a [u8]>,
218 mut f: impl FnMut(&'a [u8]),
219 ) -> Result<usize> {
220 let Some(at) = self.hash_slot(key)? else {
221 return Ok(0);
222 };
223 let hash = self
224 .hashes
225 .get_mut(at)
226 .expect("the record points at its body");
227 let mut gone = 0;
228 for field in fields {
229 if hash.remove(field) {
230 gone += 1;
231 f(field);
232 }
233 }
234 if hash.is_empty() {
235 self.drop_key(key);
236 }
237 Ok(gone)
238 }
239
240 pub fn hexpire<'a, F>(
256 &mut self,
257 key: &[u8],
258 at: u64,
259 cond: Cond,
260 fields: impl Iterator<Item = &'a [u8]>,
261 mut f: F,
262 ) -> Result<()>
263 where
264 F: FnMut(Applied),
265 {
266 if !ttl::valid_at(at) {
267 return Err(Error::new(Code::Invalid, BAD_EXPIRE));
268 }
269 let Some(slot) = self.hash_slot(key)? else {
270 for _ in fields {
271 f(Applied::Missing);
272 }
273 return Ok(());
274 };
275 let now = self.clock.now_ms();
276 let listed = self.hash_at(slot).takes_deadlines();
277 let mut emptied = false;
278 for field in fields {
279 let hash = self.hash_at_mut(slot);
280 let applied = hash.expire(field, at, cond, now);
281 emptied = hash.is_empty();
282 f(applied);
283 }
284 if emptied {
285 self.drop_key(key);
286 } else {
287 self.watch_fields(key, slot, listed);
288 }
289 Ok(())
290 }
291
292 pub fn httl<'a, F>(
299 &mut self,
300 key: &[u8],
301 fields: impl Iterator<Item = &'a [u8]>,
302 mut f: F,
303 ) -> Result<()>
304 where
305 F: FnMut(Ask),
306 {
307 let slot = self.hash_slot(key)?;
308 for field in fields {
309 match slot {
310 Some(at) => f(self.hash_at(at).deadline(field)),
311 None => f(Ask::Missing),
312 }
313 }
314 Ok(())
315 }
316
317 pub fn hpersist<'a, F>(
322 &mut self,
323 key: &[u8],
324 fields: impl Iterator<Item = &'a [u8]>,
325 mut f: F,
326 ) -> Result<()>
327 where
328 F: FnMut(Ask),
329 {
330 let slot = self.hash_slot(key)?;
331 for field in fields {
332 match slot {
333 Some(at) => f(self.hash_at_mut(at).persist(field)),
334 None => f(Ask::Missing),
335 }
336 }
337 Ok(())
338 }
339
340 pub fn hgetdel<'a, F>(
350 &mut self,
351 key: &[u8],
352 fields: impl Iterator<Item = &'a [u8]>,
353 mut f: F,
354 ) -> Result<()>
355 where
356 F: FnMut(Option<Text<'_>>),
357 {
358 let Some(slot) = self.hash_slot(key)? else {
359 for _ in fields {
360 f(None);
361 }
362 return Ok(());
363 };
364 for field in fields {
365 let hash = self.hash_at_mut(slot);
366 f(hash.get(field));
367 hash.remove(field);
368 }
369 if self.hash_at(slot).is_empty() {
370 self.drop_key(key);
371 }
372 Ok(())
373 }
374
375 pub fn hgetex<'a, F>(
390 &mut self,
391 key: &[u8],
392 expire: strings::Expire,
393 fields: impl Iterator<Item = &'a [u8]>,
394 mut f: F,
395 ) -> Result<()>
396 where
397 F: FnMut(Option<Text<'_>>),
398 {
399 check_at(expire)?;
402 let Some(slot) = self.hash_slot(key)? else {
403 for _ in fields {
404 f(None);
405 }
406 return Ok(());
407 };
408 let now = self.clock.now_ms();
409 let listed = self.hash_at(slot).takes_deadlines();
410 for field in fields {
411 let hash = self.hash_at_mut(slot);
412 f(hash.get(field));
413 match expire {
414 strings::Expire::Keep => {}
415 strings::Expire::Clear => {
416 hash.persist(field);
417 }
418 strings::Expire::At(at) => {
423 hash.expire(field, at, Cond::Always, now);
424 }
425 }
426 }
427 if self.hash_at(slot).is_empty() {
428 self.drop_key(key);
429 } else {
430 self.watch_fields(key, slot, listed);
431 }
432 Ok(())
433 }
434
435 pub fn hsetex<'a>(
452 &mut self,
453 key: &[u8],
454 exists: strings::Exists,
455 expire: strings::Expire,
456 pairs: impl Iterator<Item = (&'a [u8], &'a [u8])> + Clone,
457 ) -> Result<bool> {
458 for (f, v) in pairs.clone() {
459 strings::check_len(key, f.len())?;
460 strings::check_len(key, v.len())?;
461 }
462 check_at(expire)?;
463
464 let slot = self.hash_slot(key)?;
465 let met = match exists {
469 strings::Exists::Always => true,
470 strings::Exists::IfMissing => {
471 slot.is_none_or(|at| pairs.clone().all(|(f, _)| !self.hash_at(at).contains(f)))
472 }
473 strings::Exists::IfPresent => {
474 slot.is_some_and(|at| pairs.clone().all(|(f, _)| self.hash_at(at).contains(f)))
475 }
476 };
477 if !met {
478 return Ok(false);
479 }
480 let slot = match slot {
481 Some(at) => at,
482 None => {
483 if pairs.clone().next().is_none() {
484 return Ok(false);
485 }
486 self.new_hash(key, pairs.clone().count())
487 }
488 };
489
490 let limits = self.hash_limits;
491 let now = self.clock.now_ms();
492 let listed = self.hash_at(slot).takes_deadlines();
493 for (field, value) in pairs {
494 let hash = self.hash_at_mut(slot);
495 let kept = match expire {
500 strings::Expire::Keep => hash.deadline(field),
501 _ => Ask::Missing,
502 };
503 hash.set(field, value, &limits);
504 match expire {
505 strings::Expire::Clear => {}
506 strings::Expire::Keep => {
507 if let Ask::At(at) = kept {
508 hash.expire(field, at, Cond::Always, now);
509 }
510 }
511 strings::Expire::At(at) => {
512 hash.expire(field, at, Cond::Always, now);
513 }
514 }
515 }
516 if self.hash_at(slot).is_empty() {
517 self.drop_key(key);
518 } else {
519 self.watch_fields(key, slot, listed);
520 }
521 Ok(true)
522 }
523
524 pub fn hlen(&mut self, key: &[u8]) -> Result<usize> {
526 match self.hash_slot(key)? {
527 Some(at) => Ok(self.hash_at(at).len()),
528 None => Ok(0),
529 }
530 }
531
532 pub fn hexists(&mut self, key: &[u8], field: &[u8]) -> Result<bool> {
534 match self.hash_slot(key)? {
535 Some(at) => Ok(self.hash_at(at).contains(field)),
536 None => Ok(false),
537 }
538 }
539
540 pub fn hstrlen(&mut self, key: &[u8], field: &[u8]) -> Result<usize> {
545 match self.hash_slot(key)? {
546 Some(at) => Ok(self.hash_at(at).value_len(field).unwrap_or(0)),
547 None => Ok(0),
548 }
549 }
550
551 pub fn hgetall<F>(&mut self, key: &[u8], mut f: F) -> Result<bool>
562 where
563 F: FnMut(Text<'_>, Text<'_>),
564 {
565 self.with_hash(key, |hash| match hash {
566 Some(h) => {
567 for (field, value) in h.iter() {
568 f(field, value);
569 }
570 true
571 }
572 None => false,
573 })
574 }
575
576 pub fn with_hash<R>(&mut self, key: &[u8], f: impl FnOnce(Option<&Hash>) -> R) -> Result<R> {
587 let at = self.hash_slot(key)?;
588 Ok(f(at.map(|at| self.hash_at(at))))
589 }
590
591 pub fn hscan<F>(&mut self, key: &[u8], cursor: Cursor, count: usize, f: F) -> Result<Cursor>
596 where
597 F: FnMut(Text<'_>, Text<'_>),
598 {
599 let Some(at) = self.hash_slot(key)? else {
600 return Ok(Cursor::END);
601 };
602 Ok(self.hash_at(at).scan(cursor, count, f))
603 }
604
605 pub fn hincrby(&mut self, key: &[u8], field: &[u8], by: i64) -> Result<i64> {
613 strings::check_len(key, field.len())?;
614 let at = match self.hash_slot(key)? {
615 Some(at) => at,
616 None => self.new_hash(key, 1),
617 };
618 let current = match self.hash_at(at).get(field) {
619 Some(Text::Int(n)) => n,
620 Some(Text::Str(s)) => {
621 parse_i64(s).ok_or_else(|| Error::new(Code::Invalid, NOT_AN_INT))?
622 }
623 None => 0,
624 };
625 let next = current
626 .checked_add(by)
627 .ok_or_else(|| Error::new(Code::Invalid, WOULD_OVERFLOW))?;
628
629 let mut buf = [0u8; yo_common::num::DIGITS_MAX];
630 let text = yo_common::num::i64_digits(&mut buf, next);
631 let limits = self.hash_limits;
632 self.hashes
633 .get_mut(at)
634 .expect("the record points at its body")
635 .set(field, text, &limits);
636 Ok(next)
637 }
638
639 pub fn hincrbyfloat(&mut self, key: &[u8], field: &[u8], by: f64) -> Result<f64> {
647 strings::check_len(key, field.len())?;
648 let at = match self.hash_slot(key)? {
649 Some(at) => at,
650 None => self.new_hash(key, 1),
651 };
652 let current = match self.hash_at(at).get(field) {
653 Some(Text::Int(n)) => n as f64,
654 Some(Text::Str(s)) => {
655 parse_f64(s).ok_or_else(|| Error::new(Code::Invalid, NOT_A_FLOAT))?
656 }
657 None => 0.0,
658 };
659 let next = current + by;
660 if !next.is_finite() {
661 return Err(Error::new(
662 Code::Invalid,
663 "increment would produce NaN or Infinity",
664 ));
665 }
666
667 let mut buf = [0u8; yo_common::num::DOUBLE_MAX];
668 let text = yo_common::num::write_double(&mut buf, next);
669 let limits = self.hash_limits;
670 self.hashes
671 .get_mut(at)
672 .expect("the record points at its body")
673 .set(field, text, &limits);
674 Ok(next)
675 }
676
677 pub fn hrandfield<R>(
682 &mut self,
683 key: &[u8],
684 f: impl FnOnce(Option<(Text<'_>, Text<'_>)>) -> R,
685 ) -> Result<R> {
686 let Some(at) = self.hash_slot(key)? else {
687 return Ok(f(None));
688 };
689 let pick = self.rng.below(self.hash_at(at).len());
690 Ok(f(self.hash_at(at).at(pick)))
691 }
692
693 pub fn hrandfield_n<F>(&mut self, key: &[u8], count: i64, mut f: F) -> Result<()>
713 where
714 F: FnMut(Text<'_>, Text<'_>),
715 {
716 let Some(at) = self.hash_slot(key)? else {
717 return Ok(());
718 };
719 let rng = &mut self.rng;
723 let hash = self.hashes.get(at).expect("the record points at its body");
724 let len = hash.len();
725
726 let Ok(want) = usize::try_from(count) else {
727 let repeats = usize::try_from(count.unsigned_abs()).unwrap_or(usize::MAX);
728 for _ in 0..repeats {
729 let (field, value) = hash
730 .at(rng.below(len))
731 .expect("the draw was under the length");
732 f(field, value);
733 }
734 return Ok(());
735 };
736
737 let mut left = want.min(len);
738 let mut seen = len;
739 for i in 0..len {
740 if left == 0 {
741 break;
742 }
743 if rng.below(seen) < left {
746 let (field, value) = hash.at(i).expect("i is under the length");
747 f(field, value);
748 left -= 1;
749 }
750 seen -= 1;
751 }
752 Ok(())
753 }
754
755 fn hash_slot(&mut self, key: &[u8]) -> Result<Option<u32>> {
762 let Some(at) = self.live_slot(key, Kind::Hash)? else {
763 return Ok(None);
764 };
765 let now = self.clock.now_ms();
770 if self.reap_fields(key, at, now, false) {
771 return Ok(None);
772 }
773 Ok(Some(at))
774 }
775
776 pub(crate) fn reap_fields(&mut self, key: &[u8], at: u32, now: u64, active: bool) -> bool {
791 let mut gone = 0u64;
792 let hash = self
793 .hashes
794 .get_mut(at)
795 .expect("the record points at its body");
796 hash.reap(now, |field| {
797 gone += 1;
798 news::say_of(key, news::What::FieldExpired, field);
799 });
800 if gone == 0 {
801 return false;
802 }
803 self.expired_fields += gone;
804 if active {
805 self.expired_fields_active += gone;
806 }
807 if !self.hash_at(at).is_empty() {
808 return false;
809 }
810 self.drop_key(key);
813 news::say(key, news::What::Deleted);
814 true
815 }
816
817 fn watch_fields(&mut self, key: &[u8], at: u32, listed: bool) {
833 if listed || !self.hash_at(at).takes_deadlines() {
834 return;
835 }
836 if self
837 .field_deadlines
838 .last()
839 .is_some_and(|last| **last == *key)
840 {
841 return;
842 }
843 self.field_deadlines.push(key.into());
844 }
845
846 #[inline]
848 fn hash_at_mut(&mut self, at: u32) -> &mut Hash {
849 self.hashes
850 .get_mut(at)
851 .expect("the record points at its body")
852 }
853
854 #[inline]
860 fn hash_at(&self, at: u32) -> &Hash {
861 self.hashes.get(at).expect("the record points at its body")
862 }
863
864 fn new_hash(&mut self, key: &[u8], hint: usize) -> u32 {
870 let at =
874 yo_alloc::first_touch(|| self.hashes.insert(Hash::with_hint(hint, &self.hash_limits)));
875 let len = value::slot_record_len(false);
876 self.write_rec(key, len, |out| {
877 value::write_slot_record(out, Kind::Hash, at, None);
878 });
879 self.bodies += 1;
880 at
881 }
882}
883
884fn check_at(expire: strings::Expire) -> Result<()> {
891 match expire {
892 strings::Expire::At(at) if !ttl::valid_at(at) => Err(Error::new(Code::Invalid, BAD_EXPIRE)),
893 _ => Ok(()),
894 }
895}
896
897#[cfg(test)]
898mod tests {
899 use super::*;
900 use crate::hash::Encoding;
901 use crate::{Clock, many};
902
903 fn db() -> Keyspace {
904 Keyspace::with_clock(Clock::fixed(1_000))
905 }
906
907 fn promoting() -> (Keyspace, u32) {
915 let mut d = db();
916 if cfg!(miri) {
917 d.set_hash_limits(crate::hash::Limits {
918 max_listpack_entries: 40,
919 ..crate::hash::Limits::DEFAULT
920 });
921 return (d, 50);
922 }
923 (d, 600)
924 }
925
926 fn set(d: &mut Keyspace, key: &[u8], pairs: &[(&[u8], &[u8])]) -> usize {
927 d.hset(key, pairs.iter().copied()).expect("a hash")
928 }
929
930 fn get(d: &mut Keyspace, key: &[u8], field: &[u8]) -> Option<String> {
931 d.hget(key, field, |t| t.map(|t| text(&t))).expect("a hash")
932 }
933
934 fn text(t: &Text<'_>) -> String {
935 String::from_utf8(t.to_vec()).expect("utf8 in these tests")
936 }
937
938 fn all(d: &mut Keyspace, key: &[u8]) -> Vec<(String, String)> {
939 let mut out = Vec::new();
940 d.hgetall(key, |f, v| out.push((text(&f), text(&v))))
941 .expect("a hash");
942 out.sort();
943 out
944 }
945
946 fn expire(d: &mut Keyspace, key: &[u8], at: u64, fields: &[&[u8]]) -> Vec<Applied> {
947 let mut out = Vec::new();
948 d.hexpire(key, at, Cond::Always, fields.iter().copied(), |a| {
949 out.push(a);
950 })
951 .expect("a hash");
952 out
953 }
954
955 fn ttl_of(d: &mut Keyspace, key: &[u8], fields: &[&[u8]]) -> Vec<Ask> {
956 let mut out = Vec::new();
957 d.httl(key, fields.iter().copied(), |a| out.push(a))
958 .expect("a hash");
959 out
960 }
961
962 #[test]
963 fn setting_a_field_on_a_key_that_is_not_there_makes_it() {
964 let mut d = db();
965 assert_eq!(set(&mut d, b"h", &[(b"f", b"v")]), 1);
966 assert_eq!(d.kind_of(b"h"), Some(Kind::Hash));
967 assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("v"));
968 }
969
970 #[test]
971 fn writing_a_field_again_is_not_a_new_field() {
972 let mut d = db();
973 assert_eq!(set(&mut d, b"h", &[(b"f", b"one"), (b"g", b"two")]), 2);
974 assert_eq!(set(&mut d, b"h", &[(b"f", b"three")]), 0, "f was there");
975 assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("three"));
976 assert_eq!(d.hlen(b"h").expect("a hash"), 2);
977 }
978
979 #[test]
980 fn an_empty_write_does_not_make_a_key() {
981 let mut d = db();
982 let none: [(&[u8], &[u8]); 0] = [];
983 assert_eq!(d.hset(b"h", none.iter().copied()).expect("ok"), 0);
984 assert_eq!(d.kind_of(b"h"), None, "an empty hash does not exist");
985 }
986
987 #[test]
988 fn losing_the_last_field_loses_the_key() {
989 let mut d = db();
990 set(&mut d, b"h", &[(b"f", b"v"), (b"g", b"w")]);
991 assert_eq!(d.hdel(b"h", [b"f".as_slice()].into_iter()).expect("ok"), 1);
992 assert_eq!(d.kind_of(b"h"), Some(Kind::Hash), "g is still there");
993 assert_eq!(d.hdel(b"h", [b"g".as_slice()].into_iter()).expect("ok"), 1);
994 assert_eq!(d.kind_of(b"h"), None, "and now nothing is");
995 assert_eq!(d.len(), 0);
996 }
997
998 #[test]
999 fn every_command_says_wrongtype_for_a_string() {
1000 let mut d = db();
1001 d.set_plain(b"s", b"v").expect("room");
1002
1003 assert_eq!(
1004 d.hset(b"s", [(b"f".as_slice(), b"v".as_slice())].into_iter())
1005 .unwrap_err()
1006 .code(),
1007 Code::WrongType
1008 );
1009 assert!(d.hget(b"s", b"f", |_| ()).is_err());
1010 assert!(d.hdel(b"s", [b"f".as_slice()].into_iter()).is_err());
1011 assert!(d.hlen(b"s").is_err());
1012 assert!(d.hexists(b"s", b"f").is_err());
1013 assert!(d.hstrlen(b"s", b"f").is_err());
1014 assert!(d.hgetall(b"s", |_, _| ()).is_err());
1015 assert!(d.hsetnx(b"s", b"f", b"v").is_err());
1016 assert!(d.hincrby(b"s", b"f", 1).is_err());
1017 assert!(d.hincrbyfloat(b"s", b"f", 1.0).is_err());
1018 assert!(d.hrandfield(b"s", |_| ()).is_err());
1019 assert!(d.hrandfield_n(b"s", 1, |_, _| ()).is_err());
1020 assert!(d.hscan(b"s", Cursor::START, 10, |_, _| ()).is_err());
1021 assert!(
1022 d.hmget(b"s", [b"f".as_slice()].into_iter(), |_| ())
1023 .is_err()
1024 );
1025
1026 assert_eq!(
1027 d.kind_of(b"s"),
1028 Some(Kind::String),
1029 "and none of them wrote anything"
1030 );
1031 }
1032
1033 #[test]
1034 fn a_missing_key_reads_as_an_empty_hash() {
1035 let mut d = db();
1036 assert_eq!(d.hlen(b"nope").expect("ok"), 0);
1037 assert!(!d.hexists(b"nope", b"f").expect("ok"));
1038 assert_eq!(d.hstrlen(b"nope", b"f").expect("ok"), 0);
1039 assert_eq!(get(&mut d, b"nope", b"f"), None);
1040 assert!(!d.hgetall(b"nope", |_, _| ()).expect("ok"));
1041 assert_eq!(
1042 d.hdel(b"nope", [b"f".as_slice()].into_iter()).expect("ok"),
1043 0
1044 );
1045 }
1046
1047 #[test]
1048 fn hmget_answers_once_per_field_asked_for() {
1049 let mut d = db();
1050 set(&mut d, b"h", &[(b"a", b"1"), (b"c", b"3")]);
1051
1052 let mut got = Vec::new();
1053 d.hmget(b"h", [b"a".as_slice(), b"b", b"c"].into_iter(), |t| {
1054 got.push(t.map(|t| text(&t)));
1055 })
1056 .expect("a hash");
1057 assert_eq!(
1058 got,
1059 vec![Some("1".into()), None, Some("3".into())],
1060 "the reply is positional, so b gets a nil and not a gap"
1061 );
1062
1063 let mut missing = Vec::new();
1064 d.hmget(b"gone", [b"a".as_slice(), b"b"].into_iter(), |t| {
1065 missing.push(t.is_none());
1066 })
1067 .expect("no key");
1068 assert_eq!(missing, vec![true, true], "a missing key is all nils");
1069 }
1070
1071 #[test]
1072 fn hsetnx_writes_only_a_field_that_is_not_there() {
1073 let mut d = db();
1074 assert!(d.hsetnx(b"h", b"f", b"one").expect("ok"), "made the key");
1075 assert!(!d.hsetnx(b"h", b"f", b"two").expect("ok"), "f was there");
1076 assert_eq!(get(&mut d, b"h", b"f").as_deref(), Some("one"));
1077 assert!(
1078 d.hsetnx(b"h", b"g", b"two").expect("ok"),
1079 "and it is per field, not per key"
1080 );
1081 assert_eq!(d.hlen(b"h").expect("ok"), 2);
1082 }
1083
1084 #[test]
1085 fn hstrlen_counts_a_number_without_writing_it() {
1086 let mut d = db();
1087 set(&mut d, b"h", &[(b"n", b"-12345"), (b"s", b"hello")]);
1088 assert_eq!(d.hstrlen(b"h", b"n").expect("ok"), 6);
1089 assert_eq!(d.hstrlen(b"h", b"s").expect("ok"), 5);
1090 assert_eq!(d.hstrlen(b"h", b"nope").expect("ok"), 0);
1091 }
1092
1093 #[test]
1094 fn incrementing_counts_up_from_nothing_and_refuses_what_is_not_a_number() {
1095 let mut d = db();
1096 assert_eq!(d.hincrby(b"h", b"n", 5).expect("ok"), 5, "absent is zero");
1097 assert_eq!(d.hincrby(b"h", b"n", -7).expect("ok"), -2);
1098 assert_eq!(get(&mut d, b"h", b"n").as_deref(), Some("-2"));
1099
1100 set(&mut d, b"h", &[(b"s", b"words")]);
1101 let err = d.hincrby(b"h", b"s", 1).unwrap_err();
1102 assert_eq!(err.code(), Code::Invalid);
1103 assert_eq!(err.message(), NOT_AN_INT);
1104 assert_eq!(
1105 get(&mut d, b"h", b"s").as_deref(),
1106 Some("words"),
1107 "and it left the field alone"
1108 );
1109 }
1110
1111 #[test]
1112 fn an_increment_that_leaves_the_range_is_refused_and_not_wrapped() {
1113 let mut d = db();
1114 let max = i64::MAX.to_string();
1115 set(&mut d, b"h", &[(b"n", max.as_bytes())]);
1116 let err = d.hincrby(b"h", b"n", 1).unwrap_err();
1117 assert_eq!(err.message(), WOULD_OVERFLOW);
1118 assert_eq!(
1119 get(&mut d, b"h", b"n").as_deref(),
1120 Some(max.as_str()),
1121 "the field still holds what it held"
1122 );
1123 }
1124
1125 #[test]
1126 fn incrementing_by_a_float_reports_the_sum_and_refuses_infinity() {
1127 let mut d = db();
1128 assert!((d.hincrbyfloat(b"h", b"f", 10.5).expect("ok") - 10.5).abs() < 1e-9);
1129 assert!((d.hincrbyfloat(b"h", b"f", 0.1).expect("ok") - 10.6).abs() < 1e-9);
1130
1131 let err = d.hincrbyfloat(b"h", b"f", f64::INFINITY).unwrap_err();
1132 assert_eq!(err.message(), "increment would produce NaN or Infinity");
1133
1134 set(&mut d, b"h", &[(b"s", b"words")]);
1135 assert_eq!(
1136 d.hincrbyfloat(b"h", b"s", 1.0).unwrap_err().message(),
1137 NOT_A_FLOAT
1138 );
1139 }
1140
1141 #[test]
1142 fn a_hash_promotes_in_the_keyspace_and_object_encoding_says_so() {
1143 let (mut d, n) = promoting();
1144 set(&mut d, b"h", &[(b"f", b"v")]);
1145 assert_eq!(d.hash_encoding(b"h"), Some(Encoding::Listpack));
1146 assert_eq!(d.encoding_name(b"h"), Some("listpack"));
1147
1148 for i in 0..n {
1149 let f = format!("field-{i}");
1150 set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1151 }
1152 assert_eq!(d.hash_encoding(b"h"), Some(Encoding::Hashtable));
1153 assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1154 assert_eq!(d.hlen(b"h").expect("ok"), n as usize + 1);
1155 assert_eq!(
1156 d.hash_encoding(b"missing"),
1157 None,
1158 "and a key that is not a hash has no hash encoding"
1159 );
1160 }
1161
1162 #[test]
1163 fn a_hash_survives_being_given_a_deadline_and_goes_when_it_passes() {
1164 let mut d = db();
1165 set(&mut d, b"h", &[(b"f", b"v"), (b"g", b"w")]);
1166 assert!(d.set_expiry(b"h", Some(1_100)));
1167 assert_eq!(
1168 all(&mut d, b"h"),
1169 vec![("f".into(), "v".into()), ("g".into(), "w".into())],
1170 "writing the record did not touch the body"
1171 );
1172
1173 d.clock().advance(100);
1174 assert_eq!(d.kind_of(b"h"), None);
1175 assert_eq!(d.len(), 0);
1176 assert_eq!(d.expired_keys(), 1);
1177 }
1178
1179 #[test]
1180 fn writing_a_string_over_a_hash_gives_the_body_back() {
1181 let mut d = db();
1182 for i in 0..many(300u32) {
1183 let f = format!("field-{i}");
1184 set(&mut d, b"h", &[(f.as_bytes(), b"a value of some length")]);
1185 }
1186 assert_eq!(d.hashes.len(), 1);
1187 let held = d.memory_bytes();
1188 d.set_plain(b"h", b"now a string").expect("room");
1189
1190 assert_eq!(d.kind_of(b"h"), Some(Kind::String));
1191 assert_eq!(d.hashes.len(), 0, "the body went with the record");
1196 assert!(d.memory_bytes() < held, "and its bytes went with it");
1197 }
1198
1199 #[test]
1200 fn a_scan_walks_a_hash_in_the_keyspace_exactly_once() {
1201 let (n, page) = if cfg!(miri) { (150u32, 10) } else { (500, 32) };
1205 let mut d = db();
1206 for i in 0..n {
1207 let f = format!("field-{i}");
1208 let v = format!("value-{i}");
1209 set(&mut d, b"h", &[(f.as_bytes(), v.as_bytes())]);
1210 }
1211
1212 let mut seen: Vec<(String, String)> = Vec::new();
1213 let mut cursor = Cursor::START;
1214 loop {
1215 cursor = d
1216 .hscan(b"h", cursor, page, |f, v| seen.push((text(&f), text(&v))))
1217 .expect("a hash");
1218 if cursor == Cursor::END {
1219 break;
1220 }
1221 }
1222 seen.sort();
1223 seen.dedup();
1224 assert_eq!(seen.len(), n as usize, "every field once and only once");
1225 for (f, v) in &seen {
1226 assert_eq!(
1227 f.strip_prefix("field-"),
1228 v.strip_prefix("value-"),
1229 "and paired with its own value"
1230 );
1231 }
1232 }
1233
1234 #[test]
1235 fn a_draw_takes_the_count_asked_for_and_repeats_only_when_told_to() {
1236 let mut d = db();
1237 d.seed(7);
1238 for i in 0..10u32 {
1239 let f = format!("f{i}");
1240 set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1241 }
1242
1243 let mut got = Vec::new();
1244 d.hrandfield_n(b"h", 4, |f, _| got.push(text(&f)))
1245 .expect("ok");
1246 assert_eq!(got.len(), 4);
1247 got.sort();
1248 got.dedup();
1249 assert_eq!(got.len(), 4, "a positive count is distinct");
1250
1251 let mut over = Vec::new();
1252 d.hrandfield_n(b"h", 25, |f, _| over.push(text(&f)))
1253 .expect("ok");
1254 assert_eq!(over.len(), 10, "and never more than the hash holds");
1255
1256 let mut with_repeats = Vec::new();
1257 d.hrandfield_n(b"h", -25, |f, _| with_repeats.push(text(&f)))
1258 .expect("ok");
1259 assert_eq!(
1260 with_repeats.len(),
1261 25,
1262 "a negative count is exactly that many, repeats and all"
1263 );
1264
1265 let one = d
1266 .hrandfield(b"h", |p| p.map(|(f, _)| text(&f)))
1267 .expect("ok");
1268 assert!(one.is_some());
1269 assert!(
1270 d.hrandfield(b"gone", |p| p.is_none()).expect("ok"),
1271 "and a missing key draws a nil"
1272 );
1273 }
1274
1275 #[test]
1276 fn a_field_deadline_goes_on_and_is_reported_back() {
1277 let mut d = db();
1278 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1279 assert_eq!(
1280 expire(&mut d, b"h", 5_000, &[b"a", b"nope"]),
1281 [Applied::Ok, Applied::Missing],
1282 "one call per field, in the order asked"
1283 );
1284 assert_eq!(
1285 ttl_of(&mut d, b"h", &[b"a", b"b", b"nope"]),
1286 [Ask::At(5_000), Ask::NoDeadline, Ask::Missing]
1287 );
1288 assert_eq!(
1289 d.encoding_name(b"h"),
1290 Some("listpackex"),
1291 "and the band widened to hold it"
1292 );
1293 }
1294
1295 #[test]
1296 fn a_field_is_gone_the_next_time_the_key_is_touched() {
1297 let mut d = db();
1298 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1299 expire(&mut d, b"h", 2_000, &[b"a"]);
1300
1301 assert_eq!(d.hlen(b"h").expect("ok"), 2, "still there at 1000");
1302 d.clock().advance(1_000);
1303 assert_eq!(d.hlen(b"h").expect("ok"), 1, "and gone at 2000");
1304 assert_eq!(get(&mut d, b"h", b"a"), None);
1305 assert_eq!(get(&mut d, b"h", b"b").as_deref(), Some("2"));
1306 assert_eq!(all(&mut d, b"h"), [("b".to_owned(), "2".to_owned())]);
1307 }
1308
1309 #[test]
1310 fn the_key_goes_when_its_last_field_expires() {
1311 let mut d = db();
1312 set(&mut d, b"h", &[(b"a", b"1")]);
1313 expire(&mut d, b"h", 2_000, &[b"a"]);
1314 assert_eq!(d.kind_of(b"h"), Some(Kind::Hash));
1315
1316 d.clock().advance(1_000);
1317 assert_eq!(d.hlen(b"h").expect("ok"), 0);
1318 assert_eq!(d.kind_of(b"h"), None, "an empty hash is not stored");
1319 assert_eq!(d.len(), 0);
1320 }
1321
1322 #[test]
1325 fn a_deadline_already_past_deletes_the_field_now() {
1326 let mut d = db();
1327 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1328 assert_eq!(expire(&mut d, b"h", 500, &[b"a"]), [Applied::Deleted]);
1329 assert_eq!(d.hlen(b"h").expect("ok"), 1);
1330
1331 assert_eq!(expire(&mut d, b"h", 500, &[b"b"]), [Applied::Deleted]);
1332 assert_eq!(d.kind_of(b"h"), None);
1333 }
1334
1335 #[test]
1336 fn persisting_puts_the_field_back_to_no_deadline() {
1337 let mut d = db();
1338 set(&mut d, b"h", &[(b"a", b"1")]);
1339 expire(&mut d, b"h", 5_000, &[b"a"]);
1340
1341 let mut out = Vec::new();
1342 d.hpersist(
1343 b"h",
1344 [b"a".as_slice(), b"nope".as_slice()].into_iter(),
1345 |a| {
1346 out.push(a);
1347 },
1348 )
1349 .expect("ok");
1350 assert_eq!(out, [Ask::At(5_000), Ask::Missing]);
1351 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1352
1353 d.clock().advance(100_000);
1354 assert_eq!(d.hlen(b"h").expect("ok"), 1, "and it outlives its deadline");
1355 }
1356
1357 #[test]
1358 fn a_missing_key_answers_no_field_for_every_field_it_was_asked() {
1359 let mut d = db();
1360 assert_eq!(
1361 expire(&mut d, b"gone", 5_000, &[b"a", b"b"]),
1362 [Applied::Missing, Applied::Missing]
1363 );
1364 assert_eq!(
1365 ttl_of(&mut d, b"gone", &[b"a", b"b"]),
1366 [Ask::Missing, Ask::Missing]
1367 );
1368 assert_eq!(d.kind_of(b"gone"), None, "and asking did not create it");
1369 }
1370
1371 #[test]
1372 fn a_deadline_past_the_ceiling_is_refused_before_any_field_moves() {
1373 let mut d = db();
1374 set(&mut d, b"h", &[(b"a", b"1")]);
1375 let err = d
1376 .hexpire(
1377 b"h",
1378 crate::ttl::MAX_AT + 1,
1379 Cond::Always,
1380 [b"a".as_slice()].into_iter(),
1381 |_| unreachable!("no field is reached"),
1382 )
1383 .expect_err("past the ceiling");
1384 assert_eq!(err.code(), Code::Invalid);
1385 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1386 }
1387
1388 #[test]
1389 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
1390 let mut d = db();
1391 d.set_plain(b"s", b"v").expect("room");
1392 assert!(
1393 d.hexpire(
1394 b"s",
1395 5_000,
1396 Cond::Always,
1397 [b"a".as_slice()].into_iter(),
1398 |_| { unreachable!("nothing is reached") }
1399 )
1400 .is_err()
1401 );
1402 assert!(d.httl(b"s", [b"a".as_slice()].into_iter(), |_| {}).is_err());
1403 assert!(
1404 d.hpersist(b"s", [b"a".as_slice()].into_iter(), |_| {})
1405 .is_err()
1406 );
1407 assert_eq!(
1408 d.kind_of(b"s"),
1409 Some(Kind::String),
1410 "and the string is intact"
1411 );
1412 }
1413
1414 #[test]
1415 fn a_hash_that_never_expires_a_field_is_untouched_by_all_of_this() {
1416 let (mut d, n) = promoting();
1417 for i in 0..n {
1418 set(&mut d, b"h", &[(format!("f{i}").as_bytes(), b"v")]);
1419 }
1420 assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1421 d.clock().advance(1_000_000);
1422 assert_eq!(
1423 d.hlen(b"h").expect("ok"),
1424 n as usize,
1425 "nothing had a deadline"
1426 );
1427 }
1428
1429 fn getdel(d: &mut Keyspace, key: &[u8], fields: &[&[u8]]) -> Vec<Option<String>> {
1431 let mut out = Vec::new();
1432 d.hgetdel(key, fields.iter().copied(), |t| {
1433 out.push(t.map(|t| text(&t)));
1434 })
1435 .expect("a hash");
1436 out
1437 }
1438
1439 fn getex(
1441 d: &mut Keyspace,
1442 key: &[u8],
1443 expire: strings::Expire,
1444 fields: &[&[u8]],
1445 ) -> Vec<Option<String>> {
1446 let mut out = Vec::new();
1447 d.hgetex(key, expire, fields.iter().copied(), |t| {
1448 out.push(t.map(|t| text(&t)));
1449 })
1450 .expect("a hash");
1451 out
1452 }
1453
1454 fn setex(
1456 d: &mut Keyspace,
1457 key: &[u8],
1458 exists: strings::Exists,
1459 expire: strings::Expire,
1460 pairs: &[(&[u8], &[u8])],
1461 ) -> bool {
1462 d.hsetex(key, exists, expire, pairs.iter().copied())
1463 .expect("a hash")
1464 }
1465
1466 #[test]
1467 fn getdel_hands_the_value_back_and_then_takes_the_field() {
1468 let mut d = db();
1469 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2"), (b"c", b"3")]);
1470 assert_eq!(
1471 getdel(&mut d, b"h", &[b"a", b"nope"]),
1472 [Some("1".to_owned()), None],
1473 "positional, so a field that was not there is a hole and not a gap"
1474 );
1475 assert_eq!(all(&mut d, b"h").len(), 2);
1476 assert_eq!(
1477 getdel(&mut d, b"gone", &[b"a", b"b"]),
1478 [None, None],
1479 "and a missing key is all nils"
1480 );
1481 assert_eq!(d.kind_of(b"gone"), None, "which did not create it");
1482
1483 getdel(&mut d, b"h", &[b"b", b"c"]);
1484 assert_eq!(d.kind_of(b"h"), None, "the last field took the key with it");
1485 }
1486
1487 #[test]
1488 fn getdel_takes_the_deadline_with_the_field() {
1489 let mut d = db();
1490 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1491 expire(&mut d, b"h", 5_000, &[b"a"]);
1492 assert_eq!(getdel(&mut d, b"h", &[b"a"]), [Some("1".to_owned())]);
1493 set(&mut d, b"h", &[(b"a", b"9")]);
1494 assert_eq!(
1495 ttl_of(&mut d, b"h", &[b"a"]),
1496 [Ask::NoDeadline],
1497 "the field came back without the deadline it had"
1498 );
1499 }
1500
1501 #[test]
1502 fn getex_reads_and_moves_the_deadline_in_one_go() {
1503 let mut d = db();
1504 set(&mut d, b"h", &[(b"a", b"1")]);
1505 assert_eq!(
1506 getex(&mut d, b"h", strings::Expire::Keep, &[b"a"]),
1507 [Some("1".to_owned())]
1508 );
1509 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1510
1511 getex(&mut d, b"h", strings::Expire::At(5_000), &[b"a"]);
1512 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(5_000)]);
1513 assert_eq!(
1514 getex(&mut d, b"h", strings::Expire::Keep, &[b"a"]),
1515 [Some("1".to_owned())],
1516 "and a plain read is Keep and not Clear, which is the one place this disagrees with SET"
1517 );
1518 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(5_000)]);
1519
1520 getex(&mut d, b"h", strings::Expire::Clear, &[b"a"]);
1521 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::NoDeadline]);
1522 }
1523
1524 #[test]
1525 fn getex_hands_back_the_value_of_a_field_it_is_about_to_expire() {
1526 let mut d = db();
1527 set(&mut d, b"h", &[(b"a", b"1"), (b"b", b"2")]);
1528 assert_eq!(
1529 getex(&mut d, b"h", strings::Expire::At(1), &[b"a"]),
1530 [Some("1".to_owned())],
1531 "the read happened before the deadline was applied"
1532 );
1533 assert_eq!(get(&mut d, b"h", b"a"), None);
1534 assert_eq!(d.hlen(b"h").expect("ok"), 1);
1535
1536 getex(&mut d, b"h", strings::Expire::At(1), &[b"b"]);
1537 assert_eq!(d.kind_of(b"h"), None, "and the last one took the key");
1538 }
1539
1540 #[test]
1541 fn setex_writes_all_of_it_or_none_of_it() {
1542 let mut d = db();
1543 assert!(setex(
1544 &mut d,
1545 b"h",
1546 strings::Exists::Always,
1547 strings::Expire::Clear,
1548 &[(b"a", b"1")]
1549 ));
1550 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1551
1552 assert!(
1553 !setex(
1554 &mut d,
1555 b"h",
1556 strings::Exists::IfMissing,
1557 strings::Expire::Clear,
1558 &[(b"a", b"9"), (b"new", b"9")]
1559 ),
1560 "FNX wants every field named to be missing, and a is not"
1561 );
1562 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1563 assert_eq!(
1564 get(&mut d, b"h", b"new"),
1565 None,
1566 "and none of it was written"
1567 );
1568
1569 assert!(
1570 !setex(
1571 &mut d,
1572 b"h",
1573 strings::Exists::IfPresent,
1574 strings::Expire::Clear,
1575 &[(b"a", b"9"), (b"nope", b"9")]
1576 ),
1577 "and FXX wants every one of them to be there"
1578 );
1579 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1580
1581 assert!(setex(
1582 &mut d,
1583 b"h",
1584 strings::Exists::IfPresent,
1585 strings::Expire::Clear,
1586 &[(b"a", b"9")]
1587 ));
1588 assert_eq!(get(&mut d, b"h", b"a"), Some("9".to_owned()));
1589 }
1590
1591 #[test]
1592 fn setex_on_a_key_that_is_not_there_makes_it_only_when_it_can() {
1593 let mut d = db();
1594 assert!(
1595 !setex(
1596 &mut d,
1597 b"gone",
1598 strings::Exists::IfPresent,
1599 strings::Expire::Clear,
1600 &[(b"a", b"1")]
1601 ),
1602 "FXX cannot be met by a key with no fields at all"
1603 );
1604 assert_eq!(d.kind_of(b"gone"), None, "and it was not created");
1605
1606 assert!(setex(
1607 &mut d,
1608 b"fresh",
1609 strings::Exists::IfMissing,
1610 strings::Expire::Clear,
1611 &[(b"a", b"1")]
1612 ));
1613 assert_eq!(get(&mut d, b"fresh", b"a"), Some("1".to_owned()));
1614 }
1615
1616 #[test]
1617 fn setex_keeps_the_deadline_only_when_it_is_asked_to() {
1618 let mut d = db();
1619 set(&mut d, b"h", &[(b"a", b"1")]);
1620 expire(&mut d, b"h", 5_000, &[b"a"]);
1621
1622 setex(
1623 &mut d,
1624 b"h",
1625 strings::Exists::Always,
1626 strings::Expire::Keep,
1627 &[(b"a", b"2")],
1628 );
1629 assert_eq!(get(&mut d, b"h", b"a"), Some("2".to_owned()));
1630 assert_eq!(
1631 ttl_of(&mut d, b"h", &[b"a"]),
1632 [Ask::At(5_000)],
1633 "KEEPTTL put back what the write cleared"
1634 );
1635
1636 setex(
1637 &mut d,
1638 b"h",
1639 strings::Exists::Always,
1640 strings::Expire::Clear,
1641 &[(b"a", b"3")],
1642 );
1643 assert_eq!(
1644 ttl_of(&mut d, b"h", &[b"a"]),
1645 [Ask::NoDeadline],
1646 "and without it the write clears the deadline the way HSET does"
1647 );
1648
1649 setex(
1650 &mut d,
1651 b"h",
1652 strings::Exists::Always,
1653 strings::Expire::At(9_000),
1654 &[(b"a", b"4")],
1655 );
1656 assert_eq!(ttl_of(&mut d, b"h", &[b"a"]), [Ask::At(9_000)]);
1657 }
1658
1659 #[test]
1660 fn setex_with_a_deadline_that_has_gone_stores_and_then_removes() {
1661 let mut d = db();
1662 assert!(
1663 setex(
1664 &mut d,
1665 b"h",
1666 strings::Exists::Always,
1667 strings::Expire::At(1),
1668 &[(b"a", b"1")]
1669 ),
1670 "written, and not the separate code the HEXPIRE family has for this"
1671 );
1672 assert_eq!(
1673 d.kind_of(b"h"),
1674 None,
1675 "so a key that did not exist is still not there"
1676 );
1677
1678 set(&mut d, b"h", &[(b"keeper", b"1")]);
1679 setex(
1680 &mut d,
1681 b"h",
1682 strings::Exists::Always,
1683 strings::Expire::At(1),
1684 &[(b"a", b"1")],
1685 );
1686 assert_eq!(d.hlen(b"h").expect("ok"), 1, "and the rest of it survives");
1687 }
1688
1689 #[test]
1690 fn setex_refuses_a_deadline_past_the_ceiling_before_writing_anything() {
1691 let mut d = db();
1692 set(&mut d, b"h", &[(b"a", b"1")]);
1693 let err = d
1694 .hsetex(
1695 b"h",
1696 strings::Exists::Always,
1697 strings::Expire::At(crate::ttl::MAX_AT + 1),
1698 [(b"a".as_slice(), b"2".as_slice())].into_iter(),
1699 )
1700 .expect_err("past the ceiling");
1701 assert_eq!(err.code(), Code::Invalid);
1702 assert_eq!(get(&mut d, b"h", b"a"), Some("1".to_owned()));
1703 }
1704
1705 #[test]
1706 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
1707 let mut d = db();
1708 d.set_plain(b"s", b"v").expect("room");
1709 assert!(
1710 d.hgetdel(b"s", [b"a".as_slice()].into_iter(), |_| {})
1711 .is_err()
1712 );
1713 assert!(
1714 d.hgetex(
1715 b"s",
1716 strings::Expire::Keep,
1717 [b"a".as_slice()].into_iter(),
1718 |_| {}
1719 )
1720 .is_err()
1721 );
1722 assert!(
1723 d.hsetex(
1724 b"s",
1725 strings::Exists::Always,
1726 strings::Expire::Clear,
1727 [(b"a".as_slice(), b"1".as_slice())].into_iter(),
1728 )
1729 .is_err()
1730 );
1731 assert_eq!(d.kind_of(b"s"), Some(Kind::String));
1732 }
1733
1734 #[test]
1735 fn the_last_three_reach_a_table_the_same_way_they_reach_a_listpack() {
1736 let (mut d, n) = promoting();
1737 for i in 0..n {
1738 set(&mut d, b"h", &[(format!("f{i}").as_bytes(), b"v")]);
1739 }
1740 assert_eq!(d.encoding_name(b"h"), Some("hashtable"));
1741
1742 setex(
1743 &mut d,
1744 b"h",
1745 strings::Exists::Always,
1746 strings::Expire::At(5_000),
1747 &[(b"f0", b"x")],
1748 );
1749 assert_eq!(ttl_of(&mut d, b"h", &[b"f0"]), [Ask::At(5_000)]);
1750 assert_eq!(
1751 getex(&mut d, b"h", strings::Expire::Clear, &[b"f0"]),
1752 [Some("x".to_owned())]
1753 );
1754 assert_eq!(ttl_of(&mut d, b"h", &[b"f0"]), [Ask::NoDeadline]);
1755 assert_eq!(getdel(&mut d, b"h", &[b"f0"]), [Some("x".to_owned())]);
1756 assert_eq!(d.hlen(b"h").expect("ok"), n as usize - 1);
1757 }
1758
1759 #[test]
1760 fn a_flush_takes_the_hashes_with_it() {
1761 let mut d = db();
1762 for i in 0..many(200u32) {
1763 let f = format!("field-{i}");
1764 set(&mut d, b"h", &[(f.as_bytes(), b"v")]);
1765 }
1766 set(&mut d, b"other", &[(b"f", b"v")]);
1767 d.clear();
1768
1769 assert_eq!(d.len(), 0);
1770 assert_eq!(d.kind_of(b"h"), None);
1771 set(&mut d, b"h", &[(b"f", b"v")]);
1774 assert_eq!(d.hlen(b"h").expect("ok"), 1);
1775 }
1776}