1use compact_str::CompactString;
2
3use super::*;
4
5impl Keyspace {
6 pub fn hset(&mut self, key: &str, fields: &[(String, Bytes)]) -> Result<usize, WriteError> {
11 if fields.is_empty() {
12 return Ok(0);
13 }
14
15 self.remove_if_expired(key);
16
17 let is_new = self.ensure_collection_type(key, |v| matches!(v, Value::Hash(_)))?;
18
19 let field_increase: usize = fields
20 .iter()
21 .map(|(f, v)| f.len() + v.len() + memory::PACKED_HASH_ENTRY_OVERHEAD)
22 .sum();
23 self.reserve_memory(
24 is_new,
25 key,
26 memory::PACKED_HASH_BASE_OVERHEAD,
27 field_increase,
28 )?;
29
30 if is_new {
31 self.insert_empty(key, Value::Hash(Box::default()));
32 }
33
34 let track_access = self.track_access;
35 let added = self
36 .track_size(key, |entry| {
37 let Value::Hash(ref mut hash) = entry.value else {
38 unreachable!("type verified by ensure_collection_type");
39 };
40 let mut added = 0;
41 for (field, value) in fields {
42 if hash
43 .insert(CompactString::from(field.as_str()), value.clone())
44 .is_none()
45 {
46 added += 1;
47 }
48 }
49 entry.touch(track_access);
50 added
51 })
52 .unwrap_or(0);
53
54 Ok(added)
55 }
56
57 pub fn hget(&mut self, key: &str, field: &str) -> Result<Option<Bytes>, WrongType> {
61 let Some(entry) = self.get_live_entry(key) else {
62 return Ok(None);
63 };
64 match &entry.value {
65 Value::Hash(hash) => Ok(hash.get(field).map(Bytes::copy_from_slice)),
66 _ => Err(WrongType),
67 }
68 }
69
70 pub fn hgetall(&mut self, key: &str) -> Result<Vec<(String, Bytes)>, WrongType> {
74 let Some(entry) = self.get_live_entry(key) else {
75 return Ok(vec![]);
76 };
77 match &entry.value {
78 Value::Hash(hash) => Ok(hash
79 .iter()
80 .map(|(k, v)| (k.to_string(), Bytes::copy_from_slice(v)))
81 .collect()),
82 _ => Err(WrongType),
83 }
84 }
85
86 pub fn hdel(&mut self, key: &str, fields: &[String]) -> Result<Vec<String>, WrongType> {
90 if self.remove_if_expired(key) {
91 return Ok(vec![]);
92 }
93
94 let Some(entry) = self.entries.get_mut(key) else {
95 return Ok(vec![]);
96 };
97 if !matches!(entry.value, Value::Hash(_)) {
98 return Err(WrongType);
99 }
100
101 let old_entry_size = entry.entry_size(key);
102 let mut removed = Vec::new();
103 let mut removed_bytes: usize = 0;
104 let is_empty = if let Value::Hash(ref mut hash) = entry.value {
105 for field in fields {
106 if let Some(val) = hash.remove(field) {
107 removed_bytes += field.len() + val.len() + memory::PACKED_HASH_ENTRY_OVERHEAD;
108 removed.push(field.clone());
109 }
110 }
111 hash.is_empty()
112 } else {
113 false
114 };
115 if !removed.is_empty() {
116 self.bump_version(key);
117 }
118
119 self.cleanup_after_remove(key, old_entry_size, is_empty, removed_bytes);
120
121 Ok(removed)
122 }
123
124 pub fn hexists(&mut self, key: &str, field: &str) -> Result<bool, WrongType> {
126 let Some(entry) = self.get_live_entry(key) else {
127 return Ok(false);
128 };
129 match &entry.value {
130 Value::Hash(hash) => Ok(hash.contains_key(field)),
131 _ => Err(WrongType),
132 }
133 }
134
135 pub fn hlen(&mut self, key: &str) -> Result<usize, WrongType> {
137 if self.remove_if_expired(key) {
138 return Ok(0);
139 }
140 match self.entries.get(key) {
141 None => Ok(0),
142 Some(entry) => match &entry.value {
143 Value::Hash(hash) => Ok(hash.len()),
144 _ => Err(WrongType),
145 },
146 }
147 }
148
149 pub fn hincrby(&mut self, key: &str, field: &str, delta: i64) -> Result<i64, IncrError> {
153 self.remove_if_expired(key);
154
155 let is_new = match self.entries.get(key) {
156 None => true,
157 Some(e) if matches!(e.value, Value::Hash(_)) => false,
158 Some(_) => return Err(IncrError::WrongType),
159 };
160
161 let val_str_len = 20; let estimated_increase = if is_new {
164 memory::ENTRY_OVERHEAD
165 + key.len()
166 + memory::PACKED_HASH_BASE_OVERHEAD
167 + field.len()
168 + val_str_len
169 + memory::PACKED_HASH_ENTRY_OVERHEAD
170 } else {
171 field.len() + val_str_len + memory::PACKED_HASH_ENTRY_OVERHEAD
172 };
173
174 if !self.enforce_memory_limit(estimated_increase) {
175 return Err(IncrError::OutOfMemory);
176 }
177
178 if is_new {
179 let value = Value::Hash(Box::default());
180 self.memory.add(key, &value);
181 let entry = Entry::new(value, None);
182 self.entries.insert(CompactString::from(key), entry);
183 self.bump_version(key);
184 }
185
186 let Some(entry) = self.entries.get_mut(key) else {
188 return Err(IncrError::WrongType);
189 };
190 let old_entry_size = entry.entry_size(key);
191
192 let Value::Hash(ref mut hash) = entry.value else {
193 return Err(IncrError::WrongType);
194 };
195 let current_val = match hash.get(field) {
196 Some(data) => {
197 let s = std::str::from_utf8(data).map_err(|_| IncrError::NotAnInteger)?;
198 s.parse::<i64>().map_err(|_| IncrError::NotAnInteger)?
199 }
200 None => 0,
201 };
202 let new_val = current_val.checked_add(delta).ok_or(IncrError::Overflow)?;
203 hash.insert(field.into(), Bytes::from(new_val.to_string()));
204 entry.touch(self.track_access);
205
206 let new_value_size = memory::value_size(&entry.value);
207 entry.cached_value_size = new_value_size as u32;
208 let new_entry_size = key.len() + new_value_size + memory::ENTRY_OVERHEAD;
209 self.memory.adjust(old_entry_size, new_entry_size);
210 self.bump_version(key);
211
212 Ok(new_val)
213 }
214
215 pub fn hincrbyfloat(
221 &mut self,
222 key: &str,
223 field: &str,
224 delta: f64,
225 ) -> Result<String, IncrFloatError> {
226 self.remove_if_expired(key);
227
228 let is_new = match self.entries.get(key) {
229 None => true,
230 Some(e) if matches!(e.value, Value::Hash(_)) => false,
231 Some(_) => return Err(IncrFloatError::WrongType),
232 };
233
234 let val_str_len = 32usize;
236 let estimated_increase = if is_new {
237 memory::ENTRY_OVERHEAD
238 + key.len()
239 + memory::PACKED_HASH_BASE_OVERHEAD
240 + field.len()
241 + val_str_len
242 + memory::PACKED_HASH_ENTRY_OVERHEAD
243 } else {
244 field.len() + val_str_len + memory::PACKED_HASH_ENTRY_OVERHEAD
245 };
246
247 if !self.enforce_memory_limit(estimated_increase) {
248 return Err(IncrFloatError::OutOfMemory);
249 }
250
251 if is_new {
252 let value = Value::Hash(Box::default());
253 self.memory.add(key, &value);
254 let entry = Entry::new(value, None);
255 self.entries.insert(CompactString::from(key), entry);
256 self.bump_version(key);
257 }
258
259 let Some(entry) = self.entries.get_mut(key) else {
260 return Err(IncrFloatError::WrongType);
261 };
262 let old_entry_size = entry.entry_size(key);
263
264 let Value::Hash(ref mut hash) = entry.value else {
265 return Err(IncrFloatError::WrongType);
266 };
267 let current = match hash.get(field) {
268 Some(data) => {
269 let s = std::str::from_utf8(data).map_err(|_| IncrFloatError::NotAFloat)?;
270 s.parse::<f64>().map_err(|_| IncrFloatError::NotAFloat)?
271 }
272 None => 0.0,
273 };
274 let new_val = current + delta;
275 if new_val.is_nan() || new_val.is_infinite() {
276 return Err(IncrFloatError::NanOrInfinity);
277 }
278
279 let formatted = format_float(new_val);
280 hash.insert(field.into(), Bytes::from(formatted.clone()));
281 entry.touch(self.track_access);
282
283 let new_value_size = memory::value_size(&entry.value);
284 entry.cached_value_size = new_value_size as u32;
285 let new_entry_size = key.len() + new_value_size + memory::ENTRY_OVERHEAD;
286 self.memory.adjust(old_entry_size, new_entry_size);
287 self.bump_version(key);
288
289 Ok(formatted)
290 }
291
292 pub fn hkeys(&mut self, key: &str) -> Result<Vec<String>, WrongType> {
294 let Some(entry) = self.get_live_entry(key) else {
295 return Ok(vec![]);
296 };
297 match &entry.value {
298 Value::Hash(hash) => Ok(hash.iter().map(|(k, _)| k.to_string()).collect()),
299 _ => Err(WrongType),
300 }
301 }
302
303 pub fn hvals(&mut self, key: &str) -> Result<Vec<Bytes>, WrongType> {
305 let Some(entry) = self.get_live_entry(key) else {
306 return Ok(vec![]);
307 };
308 match &entry.value {
309 Value::Hash(hash) => Ok(hash
310 .iter()
311 .map(|(_, v)| Bytes::copy_from_slice(v))
312 .collect()),
313 _ => Err(WrongType),
314 }
315 }
316
317 pub fn scan_hash(
323 &mut self,
324 key: &str,
325 cursor: u64,
326 count: usize,
327 pattern: Option<&str>,
328 ) -> Result<(u64, Vec<(String, Bytes)>), WrongType> {
329 let Some(entry) = self.get_live_entry(key) else {
330 return Ok((0, vec![]));
331 };
332 let Value::Hash(ref hash) = entry.value else {
333 return Err(WrongType);
334 };
335
336 let target = if count == 0 { 10 } else { count };
337 let compiled = pattern.map(GlobPattern::new);
338 let mut result = Vec::with_capacity(target);
339 let mut pos = 0u64;
340 let mut done = true;
341
342 for (field, value) in hash.iter() {
343 if pos < cursor {
344 pos += 1;
345 continue;
346 }
347 if let Some(ref pat) = compiled {
348 if !pat.matches(field) {
349 pos += 1;
350 continue;
351 }
352 }
353 result.push((field.to_string(), Bytes::copy_from_slice(value)));
354 pos += 1;
355 if result.len() >= target {
356 done = false;
357 break;
358 }
359 }
360
361 Ok(if done { (0, result) } else { (pos, result) })
362 }
363
364 pub fn hmget(&mut self, key: &str, fields: &[String]) -> Result<Vec<Option<Bytes>>, WrongType> {
368 let Some(entry) = self.get_live_entry(key) else {
369 return Ok(fields.iter().map(|_| None).collect());
370 };
371 match &entry.value {
372 Value::Hash(hash) => Ok(fields
373 .iter()
374 .map(|f| hash.get(f.as_str()).map(Bytes::copy_from_slice))
375 .collect()),
376 _ => Err(WrongType),
377 }
378 }
379
380 pub fn hrandfield(
389 &mut self,
390 key: &str,
391 count: Option<i64>,
392 with_values: bool,
393 ) -> Result<Vec<(String, Option<Bytes>)>, WrongType> {
394 let Some(entry) = self.get_live_entry(key) else {
395 return Ok(vec![]);
396 };
397 let Value::Hash(ref hash) = entry.value else {
398 return Err(WrongType);
399 };
400 if hash.is_empty() {
401 return Ok(vec![]);
402 }
403
404 let fields: Vec<(&str, &[u8])> = hash.iter().collect();
406 let mut rng = rand::rng();
407
408 let result = match count {
409 None => {
410 use rand::seq::IteratorRandom;
412 fields
413 .iter()
414 .choose(&mut rng)
415 .map(|(f, _)| ((*f).to_owned(), None))
416 .into_iter()
417 .collect()
418 }
419 Some(n) if n > 0 => {
420 use rand::seq::IteratorRandom;
421 let n = (n as usize).min(fields.len());
422 fields
423 .iter()
424 .choose_multiple(&mut rng, n)
425 .into_iter()
426 .map(|(f, v)| {
427 let val = if with_values {
428 Some(Bytes::copy_from_slice(v))
429 } else {
430 None
431 };
432 ((*f).to_owned(), val)
433 })
434 .collect()
435 }
436 Some(n) => {
437 use rand::Rng;
439 let n = n.unsigned_abs() as usize;
440 (0..n)
441 .map(|_| {
442 let idx = rng.random_range(0..fields.len());
443 let (f, v) = fields[idx];
444 let val = if with_values {
445 Some(Bytes::copy_from_slice(v))
446 } else {
447 None
448 };
449 (f.to_owned(), val)
450 })
451 .collect()
452 }
453 };
454
455 Ok(result)
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 #[test]
464 fn hset_creates_hash() {
465 let mut ks = Keyspace::new();
466 let count = ks
467 .hset("h", &[("field1".into(), Bytes::from("value1"))])
468 .unwrap();
469 assert_eq!(count, 1);
470 assert_eq!(ks.value_type("h"), "hash");
471 }
472
473 #[test]
474 fn hset_returns_new_field_count() {
475 let mut ks = Keyspace::new();
476 let count = ks
478 .hset(
479 "h",
480 &[
481 ("f1".into(), Bytes::from("v1")),
482 ("f2".into(), Bytes::from("v2")),
483 ],
484 )
485 .unwrap();
486 assert_eq!(count, 2);
487
488 let count = ks
490 .hset(
491 "h",
492 &[
493 ("f1".into(), Bytes::from("updated")),
494 ("f3".into(), Bytes::from("v3")),
495 ],
496 )
497 .unwrap();
498 assert_eq!(count, 1); }
500
501 #[test]
502 fn hget_returns_value() {
503 let mut ks = Keyspace::new();
504 ks.hset("h", &[("name".into(), Bytes::from("alice"))])
505 .unwrap();
506 let val = ks.hget("h", "name").unwrap();
507 assert_eq!(val, Some(Bytes::from("alice")));
508 }
509
510 #[test]
511 fn hget_missing_field_returns_none() {
512 let mut ks = Keyspace::new();
513 ks.hset("h", &[("a".into(), Bytes::from("1"))]).unwrap();
514 assert_eq!(ks.hget("h", "b").unwrap(), None);
515 }
516
517 #[test]
518 fn hget_missing_key_returns_none() {
519 let mut ks = Keyspace::new();
520 assert_eq!(ks.hget("missing", "field").unwrap(), None);
521 }
522
523 #[test]
524 fn hgetall_returns_all_fields() {
525 let mut ks = Keyspace::new();
526 ks.hset(
527 "h",
528 &[
529 ("a".into(), Bytes::from("1")),
530 ("b".into(), Bytes::from("2")),
531 ],
532 )
533 .unwrap();
534 let mut fields = ks.hgetall("h").unwrap();
535 fields.sort_by(|a, b| a.0.cmp(&b.0));
536 assert_eq!(fields.len(), 2);
537 assert_eq!(fields[0], ("a".into(), Bytes::from("1")));
538 assert_eq!(fields[1], ("b".into(), Bytes::from("2")));
539 }
540
541 #[test]
542 fn hdel_removes_fields() {
543 let mut ks = Keyspace::new();
544 ks.hset(
545 "h",
546 &[
547 ("a".into(), Bytes::from("1")),
548 ("b".into(), Bytes::from("2")),
549 ("c".into(), Bytes::from("3")),
550 ],
551 )
552 .unwrap();
553 let removed = ks.hdel("h", &["a".into(), "c".into()]).unwrap();
554 assert_eq!(removed.len(), 2);
555 assert!(removed.contains(&"a".into()));
556 assert!(removed.contains(&"c".into()));
557 assert_eq!(ks.hlen("h").unwrap(), 1);
558 }
559
560 #[test]
561 fn hdel_auto_deletes_empty_hash() {
562 let mut ks = Keyspace::new();
563 ks.hset("h", &[("only".into(), Bytes::from("field"))])
564 .unwrap();
565 ks.hdel("h", &["only".into()]).unwrap();
566 assert_eq!(ks.value_type("h"), "none");
567 }
568
569 #[test]
570 fn hexists_returns_true_for_existing_field() {
571 let mut ks = Keyspace::new();
572 ks.hset("h", &[("field".into(), Bytes::from("val"))])
573 .unwrap();
574 assert!(ks.hexists("h", "field").unwrap());
575 }
576
577 #[test]
578 fn hexists_returns_false_for_missing_field() {
579 let mut ks = Keyspace::new();
580 ks.hset("h", &[("a".into(), Bytes::from("1"))]).unwrap();
581 assert!(!ks.hexists("h", "missing").unwrap());
582 }
583
584 #[test]
585 fn hlen_returns_field_count() {
586 let mut ks = Keyspace::new();
587 ks.hset(
588 "h",
589 &[
590 ("a".into(), Bytes::from("1")),
591 ("b".into(), Bytes::from("2")),
592 ],
593 )
594 .unwrap();
595 assert_eq!(ks.hlen("h").unwrap(), 2);
596 }
597
598 #[test]
599 fn hlen_missing_key_returns_zero() {
600 let mut ks = Keyspace::new();
601 assert_eq!(ks.hlen("missing").unwrap(), 0);
602 }
603
604 #[test]
605 fn hincrby_new_field() {
606 let mut ks = Keyspace::new();
607 ks.hset("h", &[("x".into(), Bytes::from("ignored"))])
608 .unwrap();
609 let val = ks.hincrby("h", "counter", 5).unwrap();
610 assert_eq!(val, 5);
611 }
612
613 #[test]
614 fn hincrby_existing_field() {
615 let mut ks = Keyspace::new();
616 ks.hset("h", &[("n".into(), Bytes::from("10"))]).unwrap();
617 let val = ks.hincrby("h", "n", 3).unwrap();
618 assert_eq!(val, 13);
619 }
620
621 #[test]
622 fn hincrby_negative_delta() {
623 let mut ks = Keyspace::new();
624 ks.hset("h", &[("n".into(), Bytes::from("10"))]).unwrap();
625 let val = ks.hincrby("h", "n", -7).unwrap();
626 assert_eq!(val, 3);
627 }
628
629 #[test]
630 fn hincrby_non_integer_returns_error() {
631 let mut ks = Keyspace::new();
632 ks.hset("h", &[("s".into(), Bytes::from("notanumber"))])
633 .unwrap();
634 assert_eq!(
635 ks.hincrby("h", "s", 1).unwrap_err(),
636 IncrError::NotAnInteger
637 );
638 }
639
640 #[test]
641 fn hkeys_returns_field_names() {
642 let mut ks = Keyspace::new();
643 ks.hset(
644 "h",
645 &[
646 ("alpha".into(), Bytes::from("1")),
647 ("beta".into(), Bytes::from("2")),
648 ],
649 )
650 .unwrap();
651 let mut keys = ks.hkeys("h").unwrap();
652 keys.sort();
653 assert_eq!(keys, vec!["alpha", "beta"]);
654 }
655
656 #[test]
657 fn hvals_returns_values() {
658 let mut ks = Keyspace::new();
659 ks.hset(
660 "h",
661 &[
662 ("a".into(), Bytes::from("x")),
663 ("b".into(), Bytes::from("y")),
664 ],
665 )
666 .unwrap();
667 let mut vals = ks.hvals("h").unwrap();
668 vals.sort();
669 assert_eq!(vals, vec![Bytes::from("x"), Bytes::from("y")]);
670 }
671
672 #[test]
673 fn hmget_returns_values_for_existing_fields() {
674 let mut ks = Keyspace::new();
675 ks.hset(
676 "h",
677 &[
678 ("a".into(), Bytes::from("1")),
679 ("b".into(), Bytes::from("2")),
680 ],
681 )
682 .unwrap();
683 let vals = ks
684 .hmget("h", &["a".into(), "missing".into(), "b".into()])
685 .unwrap();
686 assert_eq!(vals.len(), 3);
687 assert_eq!(vals[0], Some(Bytes::from("1")));
688 assert_eq!(vals[1], None);
689 assert_eq!(vals[2], Some(Bytes::from("2")));
690 }
691
692 #[test]
693 fn hash_on_string_key_returns_wrongtype() {
694 let mut ks = Keyspace::new();
695 ks.set("s".into(), Bytes::from("string"), None, false, false);
696 assert!(ks.hset("s", &[("f".into(), Bytes::from("v"))]).is_err());
697 assert!(ks.hget("s", "f").is_err());
698 assert!(ks.hgetall("s").is_err());
699 assert!(ks.hdel("s", &["f".into()]).is_err());
700 assert!(ks.hexists("s", "f").is_err());
701 assert!(ks.hlen("s").is_err());
702 assert!(ks.hincrby("s", "f", 1).is_err());
703 assert!(ks.hkeys("s").is_err());
704 assert!(ks.hvals("s").is_err());
705 assert!(ks.hmget("s", &["f".into()]).is_err());
706 }
707
708 #[test]
709 fn hincrby_overflow_returns_error() {
710 let mut ks = Keyspace::new();
711 ks.hset("h", &[("count".into(), Bytes::from(i64::MAX.to_string()))])
713 .unwrap();
714
715 let result = ks.hincrby("h", "count", 1);
717 assert!(result.is_err());
718 }
719
720 #[test]
721 fn hincrby_on_non_integer_returns_error() {
722 let mut ks = Keyspace::new();
723 ks.hset("h", &[("field".into(), Bytes::from("not_a_number"))])
724 .unwrap();
725
726 let result = ks.hincrby("h", "field", 1);
727 assert!(result.is_err());
728 }
729
730 #[test]
733 fn scan_hash_returns_all() {
734 let mut ks = Keyspace::new();
735 ks.hset(
736 "h",
737 &[
738 ("a".into(), Bytes::from("1")),
739 ("b".into(), Bytes::from("2")),
740 ("c".into(), Bytes::from("3")),
741 ],
742 )
743 .unwrap();
744 let (cursor, fields) = ks.scan_hash("h", 0, 100, None).unwrap();
745 assert_eq!(cursor, 0);
746 assert_eq!(fields.len(), 3);
747 }
748
749 #[test]
750 fn scan_hash_missing_key() {
751 let mut ks = Keyspace::new();
752 let (cursor, fields) = ks.scan_hash("missing", 0, 10, None).unwrap();
753 assert_eq!(cursor, 0);
754 assert!(fields.is_empty());
755 }
756
757 #[test]
758 fn scan_hash_wrong_type() {
759 let mut ks = Keyspace::new();
760 ks.set("h".into(), Bytes::from("string"), None, false, false);
761 assert!(ks.scan_hash("h", 0, 10, None).is_err());
762 }
763
764 #[test]
765 fn scan_hash_with_pattern() {
766 let mut ks = Keyspace::new();
767 ks.hset(
768 "h",
769 &[
770 ("name".into(), Bytes::from("alice")),
771 ("age".into(), Bytes::from("30")),
772 ("nickname".into(), Bytes::from("ali")),
773 ],
774 )
775 .unwrap();
776 let (_, fields) = ks.scan_hash("h", 0, 100, Some("n*")).unwrap();
777 assert_eq!(fields.len(), 2);
778 assert!(fields.iter().all(|(f, _)| f.starts_with('n')));
779 }
780
781 #[test]
782 fn scan_hash_pagination() {
783 let mut ks = Keyspace::new();
784 let fields: Vec<(String, Bytes)> = (0..20)
785 .map(|i| (format!("f{i}"), Bytes::from(format!("v{i}"))))
786 .collect();
787 ks.hset("h", &fields).unwrap();
788
789 let mut collected = Vec::new();
790 let mut cursor = 0u64;
791 loop {
792 let (next, batch) = ks.scan_hash("h", cursor, 5, None).unwrap();
793 collected.extend(batch);
794 if next == 0 {
795 break;
796 }
797 cursor = next;
798 }
799 assert_eq!(collected.len(), 20);
800 }
801
802 #[test]
803 fn hash_auto_deleted_when_empty() {
804 let mut ks = Keyspace::new();
805 ks.hset(
806 "h",
807 &[
808 ("f1".into(), Bytes::from("v1")),
809 ("f2".into(), Bytes::from("v2")),
810 ],
811 )
812 .unwrap();
813 assert_eq!(ks.len(), 1);
814
815 ks.hdel("h", &["f1".into(), "f2".into()]).unwrap();
817
818 assert_eq!(ks.len(), 0);
820 assert!(!ks.exists("h"));
821 }
822
823 #[test]
826 fn hrandfield_no_count_returns_single_field() {
827 let mut ks = Keyspace::new();
828 ks.hset(
829 "h",
830 &[
831 ("a".into(), Bytes::from("1")),
832 ("b".into(), Bytes::from("2")),
833 ("c".into(), Bytes::from("3")),
834 ],
835 )
836 .unwrap();
837 let result = ks.hrandfield("h", None, false).unwrap();
838 assert_eq!(result.len(), 1);
839 assert!(["a", "b", "c"].contains(&result[0].0.as_str()));
840 assert!(result[0].1.is_none());
842 }
843
844 #[test]
845 fn hrandfield_positive_count_distinct() {
846 let mut ks = Keyspace::new();
847 ks.hset(
848 "h",
849 &[
850 ("a".into(), Bytes::from("1")),
851 ("b".into(), Bytes::from("2")),
852 ("c".into(), Bytes::from("3")),
853 ],
854 )
855 .unwrap();
856 let result = ks.hrandfield("h", Some(2), false).unwrap();
857 assert_eq!(result.len(), 2);
858 for (f, v) in &result {
860 assert!(["a", "b", "c"].contains(&f.as_str()));
861 assert!(v.is_none());
862 }
863 let unique: std::collections::HashSet<_> = result.iter().map(|(f, _)| f).collect();
865 assert_eq!(unique.len(), 2);
866 }
867
868 #[test]
869 fn hrandfield_positive_count_capped_at_hash_size() {
870 let mut ks = Keyspace::new();
871 ks.hset(
872 "h",
873 &[
874 ("a".into(), Bytes::from("1")),
875 ("b".into(), Bytes::from("2")),
876 ],
877 )
878 .unwrap();
879 let result = ks.hrandfield("h", Some(10), false).unwrap();
880 assert_eq!(result.len(), 2);
881 }
882
883 #[test]
884 fn hrandfield_negative_count_allows_duplicates() {
885 let mut ks = Keyspace::new();
886 ks.hset("h", &[("only".into(), Bytes::from("v"))]).unwrap();
887 let result = ks.hrandfield("h", Some(-5), false).unwrap();
888 assert_eq!(result.len(), 5);
889 assert!(result.iter().all(|(f, _)| f == "only"));
890 }
891
892 #[test]
893 fn hrandfield_with_values() {
894 let mut ks = Keyspace::new();
895 ks.hset(
896 "h",
897 &[
898 ("field".into(), Bytes::from("value")),
899 ("other".into(), Bytes::from("data")),
900 ],
901 )
902 .unwrap();
903 let result = ks.hrandfield("h", Some(2), true).unwrap();
904 assert_eq!(result.len(), 2);
905 for (f, v) in &result {
906 assert!(["field", "other"].contains(&f.as_str()));
907 assert!(v.is_some());
908 }
909 }
910
911 #[test]
912 fn hrandfield_missing_key_returns_empty() {
913 let mut ks = Keyspace::new();
914 assert!(ks.hrandfield("missing", None, false).unwrap().is_empty());
915 assert!(ks.hrandfield("missing", Some(5), true).unwrap().is_empty());
916 }
917
918 #[test]
919 fn hrandfield_wrong_type_returns_error() {
920 let mut ks = Keyspace::new();
921 ks.set("s".into(), Bytes::from("val"), None, false, false);
922 assert!(ks.hrandfield("s", None, false).is_err());
923 }
924}