1use std::{
5 cmp::{Ordering, Reverse},
6 collections::{BTreeMap, BinaryHeap, HashMap, HashSet, btree_map},
7 ops::RangeBounds,
8};
9
10use reifydb_codec::key::encoded::EncodedKey;
11use reifydb_core::{common::CommitVersion, metrics::heap::HeapSize};
12use reifydb_value::reifydb_assertions;
13
14use crate::entry::{Value, entry_bytes_with};
15
16pub(super) type VersionMap = BTreeMap<Reverse<CommitVersion>, Value>;
17
18#[derive(Clone, Default)]
19pub(super) struct RowMap {
20 entries: BTreeMap<EncodedKey, VersionMap>,
21 current_bytes: u64,
22 historical_bytes: u64,
23 versions: BTreeMap<CommitVersion, u32>,
24}
25
26impl RowMap {
27 pub fn insert(&mut self, key: EncodedKey, version: CommitVersion, value: Value) {
28 let key_heap = key.heap_size();
29 let bytes = entry_bytes_with(key_heap, &value);
30
31 let versions = self.entries.entry(key).or_default();
32 let previous_newest = versions.keys().next().map(|Reverse(v)| *v);
33 let replaced = versions.insert(Reverse(version), value);
34 let stays_newest = previous_newest.is_none_or(|newest| version >= newest);
35 let demoted_bytes = previous_newest
36 .filter(|newest| version > *newest)
37 .and_then(|newest| versions.get(&Reverse(newest)))
38 .map(|demoted| entry_bytes_with(key_heap, demoted));
39
40 match replaced {
41 Some(replaced) => {
42 let replaced_bytes = entry_bytes_with(key_heap, &replaced);
43 if previous_newest == Some(version) {
44 self.current_bytes = self.current_bytes.saturating_sub(replaced_bytes);
45 } else {
46 self.historical_bytes = self.historical_bytes.saturating_sub(replaced_bytes);
47 }
48 }
49 None => *self.versions.entry(version).or_insert(0) += 1,
50 }
51
52 if let Some(demoted) = demoted_bytes {
53 self.current_bytes = self.current_bytes.saturating_sub(demoted);
54 self.historical_bytes = self.historical_bytes.saturating_add(demoted);
55 }
56
57 if stays_newest {
58 self.current_bytes = self.current_bytes.saturating_add(bytes);
59 } else {
60 self.historical_bytes = self.historical_bytes.saturating_add(bytes);
61 }
62 }
63
64 pub fn remove(&mut self, dropped: &HashMap<EncodedKey, HashSet<CommitVersion>>) -> Vec<Removed> {
65 let mut removed = Vec::new();
66 for (key, versions) in dropped {
67 let Some(held) = self.entries.get_mut(key) else {
68 continue;
69 };
70 let key_heap = key.heap_size();
71 for version in versions {
72 let was_newest = held.keys().next() == Some(&Reverse(*version));
73 let Some(value) = held.remove(&Reverse(*version)) else {
74 continue;
75 };
76 let bytes = entry_bytes_with(key_heap, &value);
77 if was_newest {
78 self.current_bytes = self.current_bytes.saturating_sub(bytes);
79 if let Some((_, promoted)) = held.iter().next() {
80 let promoted = entry_bytes_with(key_heap, promoted);
81 self.historical_bytes = self.historical_bytes.saturating_sub(promoted);
82 self.current_bytes = self.current_bytes.saturating_add(promoted);
83 }
84 } else {
85 self.historical_bytes = self.historical_bytes.saturating_sub(bytes);
86 }
87 Self::forget(&mut self.versions, *version);
88 removed.push(Removed {
89 key: key.clone(),
90 version: *version,
91 value,
92 });
93 }
94 if held.is_empty() {
95 self.entries.remove(key);
96 }
97 }
98 removed
99 }
100
101 fn forget(versions: &mut BTreeMap<CommitVersion, u32>, version: CommitVersion) {
102 let count = versions.get_mut(&version).expect("every stored version is counted");
103 *count -= 1;
104 if *count == 0 {
105 versions.remove(&version);
106 }
107 }
108
109 pub fn get(&self, key: &[u8], version: CommitVersion) -> Option<(CommitVersion, &Value)> {
110 self.entries
111 .get(key)
112 .and_then(|versions| versions.range(Reverse(version)..).next())
113 .map(|(Reverse(found), value)| (*found, value))
114 }
115
116 pub fn versions_for(&self, key: &[u8]) -> Option<&VersionMap> {
117 self.entries.get(key)
118 }
119
120 pub fn range<R>(&self, bounds: R) -> btree_map::Range<'_, EncodedKey, VersionMap>
121 where
122 R: RangeBounds<[u8]>,
123 {
124 self.entries.range::<[u8], R>(bounds)
125 }
126
127 pub fn iter(&self) -> btree_map::Iter<'_, EncodedKey, VersionMap> {
128 self.entries.iter()
129 }
130
131 pub fn key_count(&self) -> usize {
132 self.entries.len()
133 }
134
135 pub fn current_bytes(&self) -> u64 {
136 self.current_bytes
137 }
138
139 pub fn historical_bytes(&self) -> u64 {
140 self.historical_bytes
141 }
142
143 pub fn bytes(&self) -> u64 {
144 self.current_bytes.saturating_add(self.historical_bytes)
145 }
146
147 pub fn is_empty(&self) -> bool {
148 self.entries.is_empty()
149 }
150
151 pub fn min_version(&self) -> Option<CommitVersion> {
152 self.versions.keys().next().copied()
153 }
154
155 pub fn max_version(&self) -> Option<CommitVersion> {
156 self.versions.keys().next_back().copied()
157 }
158}
159
160pub(super) struct Removed {
161 pub key: EncodedKey,
162 pub version: CommitVersion,
163 pub value: Value,
164}
165
166pub(super) fn lookup<'a>(
167 maps: impl Iterator<Item = &'a RowMap>,
168 key: &[u8],
169 version: CommitVersion,
170) -> Option<(CommitVersion, &'a Value)> {
171 let mut best: Option<(CommitVersion, &'a Value)> = None;
172 for rows in maps {
173 if rows.min_version().is_none_or(|min| min > version) {
174 continue;
175 }
176 if let Some((found, _)) = best
177 && rows.max_version().is_some_and(|max| max <= found)
178 {
179 continue;
180 }
181 if let Some((found, value)) = rows.get(key, version)
182 && best.is_none_or(|(best, _)| found > best)
183 {
184 best = Some((found, value));
185 }
186 }
187 best
188}
189
190pub(super) fn newest_across<'a>(
191 maps: impl Iterator<Item = &'a VersionMap>,
192 version: CommitVersion,
193) -> Option<(CommitVersion, &'a Value)> {
194 maps.filter_map(|versions| {
195 versions.range(Reverse(version)..).next().map(|(Reverse(found), value)| (*found, value))
196 })
197 .max_by_key(|(found, _)| *found)
198}
199
200struct Head<'a> {
201 key: &'a EncodedKey,
202 versions: &'a VersionMap,
203 source: usize,
204 reverse: bool,
205}
206
207impl PartialEq for Head<'_> {
208 fn eq(&self, other: &Self) -> bool {
209 self.key == other.key
210 }
211}
212
213impl Eq for Head<'_> {}
214
215impl PartialOrd for Head<'_> {
216 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
217 Some(self.cmp(other))
218 }
219}
220
221impl Ord for Head<'_> {
222 fn cmp(&self, other: &Self) -> Ordering {
223 let order = self.key.cmp(other.key);
224 if self.reverse {
225 order
226 } else {
227 order.reverse()
228 }
229 }
230}
231
232pub(super) struct MergedRows<'a, I>
233where
234 I: Iterator<Item = (&'a EncodedKey, &'a VersionMap)>,
235{
236 iters: Vec<I>,
237 heads: BinaryHeap<Head<'a>>,
238 reverse: bool,
239 group: Vec<&'a VersionMap>,
240}
241
242impl<'a, I> MergedRows<'a, I>
243where
244 I: Iterator<Item = (&'a EncodedKey, &'a VersionMap)>,
245{
246 pub fn new(mut iters: Vec<I>, reverse: bool) -> Self {
247 let mut heads = BinaryHeap::with_capacity(iters.len());
248 for (source, iter) in iters.iter_mut().enumerate() {
249 if let Some((key, versions)) = iter.next() {
250 heads.push(Head {
251 key,
252 versions,
253 source,
254 reverse,
255 });
256 }
257 }
258 Self {
259 iters,
260 heads,
261 reverse,
262 group: Vec::new(),
263 }
264 }
265
266 fn advance(&mut self, source: usize) {
267 if let Some((key, versions)) = self.iters[source].next() {
268 self.heads.push(Head {
269 key,
270 versions,
271 source,
272 reverse: self.reverse,
273 });
274 }
275 }
276
277 pub fn next_group(&mut self) -> Option<(&'a EncodedKey, &[&'a VersionMap])> {
278 let first = self.heads.pop()?;
279 let target = first.key;
280 self.group.clear();
281 self.group.push(first.versions);
282 self.advance(first.source);
283 while self.heads.peek().is_some_and(|head| head.key == target) {
284 let head = self.heads.pop().expect("a peeked heap yields");
285 self.group.push(head.versions);
286 self.advance(head.source);
287 }
288 Some((target, &self.group))
289 }
290}
291
292#[derive(Default)]
293pub(super) struct ActiveRows {
294 rows: RowMap,
295}
296
297impl ActiveRows {
298 pub fn new() -> Self {
299 Self::default()
300 }
301
302 pub fn rows(&self) -> &RowMap {
303 &self.rows
304 }
305
306 pub fn insert(&mut self, key: EncodedKey, version: CommitVersion, value: Value) {
307 self.rows.insert(key, version, value);
308 }
309
310 pub fn min_version(&self) -> Option<CommitVersion> {
311 self.rows.min_version()
312 }
313
314 pub fn compact(&mut self, dropped: &HashMap<EncodedKey, HashSet<CommitVersion>>) -> Vec<Removed> {
315 self.rows.remove(dropped)
316 }
317
318 pub fn bytes(&self) -> u64 {
319 self.rows.bytes()
320 }
321
322 pub fn is_empty(&self) -> bool {
323 self.rows.is_empty()
324 }
325
326 pub fn close(self) -> ClosedRows {
327 reifydb_assertions! {
328 assert!(
329 !self.rows.is_empty(),
330 "closing an empty active map mints a closed map with no version range, and the flush \
331 orders closed maps by that range"
332 );
333 }
334 ClosedRows {
335 rows: self.rows,
336 }
337 }
338}
339
340#[derive(Clone)]
341pub(super) struct ClosedRows {
342 rows: RowMap,
343}
344
345impl ClosedRows {
346 pub fn rows(&self) -> &RowMap {
347 &self.rows
348 }
349
350 pub fn min_version(&self) -> CommitVersion {
351 self.rows.min_version().expect("a closed map holds at least one row")
352 }
353
354 pub fn compact(&mut self, dropped: &HashMap<EncodedKey, HashSet<CommitVersion>>) -> Vec<Removed> {
355 self.rows.remove(dropped)
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use std::ops::Bound;
362
363 use reifydb_value::util::cowvec::CowVec;
364
365 use super::*;
366
367 fn key(name: &str) -> EncodedKey {
368 EncodedKey::new(name.as_bytes().to_vec())
369 }
370
371 fn val(bytes: &str) -> Value {
372 Some(CowVec::new(bytes.as_bytes().to_vec()))
373 }
374
375 fn walked_bytes(rows: &RowMap) -> u64 {
376 rows.iter()
377 .map(|(key, versions)| {
378 let heap = key.heap_size();
379 versions.values().map(|value| entry_bytes_with(heap, value)).sum::<u64>()
380 })
381 .sum()
382 }
383
384 fn walked_version_count(rows: &RowMap) -> usize {
385 rows.iter().map(|(_, versions)| versions.len()).sum()
386 }
387
388 fn walked_current_bytes(rows: &RowMap) -> u64 {
389 rows.iter()
390 .filter_map(|(key, versions)| {
391 versions.values().next().map(|value| entry_bytes_with(key.heap_size(), value))
392 })
393 .sum()
394 }
395
396 #[test]
397 fn a_lone_version_of_a_key_is_billed_as_current() {
398 let mut rows = RowMap::default();
401 rows.insert(key("a"), CommitVersion(1), val("one"));
402
403 assert_eq!(rows.key_count(), 1);
404 assert_eq!(walked_version_count(&rows), 1);
405 assert_eq!(rows.historical_bytes(), 0, "a key with a single version has no history");
406 assert_eq!(rows.current_bytes(), walked_current_bytes(&rows));
407 }
408
409 #[test]
410 fn a_newer_version_demotes_the_previous_newest_into_history() {
411 let mut rows = RowMap::default();
414 rows.insert(key("a"), CommitVersion(1), val("one"));
415 let first = rows.current_bytes();
416 rows.insert(key("a"), CommitVersion(2), val("twotwo"));
417
418 assert_eq!(rows.key_count(), 1, "a second version is not a second key");
419 assert_eq!(walked_version_count(&rows), 2);
420 assert_eq!(rows.historical_bytes(), first, "the superseded version carries its own bytes down");
421 assert_eq!(rows.current_bytes(), walked_current_bytes(&rows));
422 assert_eq!(rows.bytes(), walked_bytes(&rows));
423 }
424
425 #[test]
426 fn a_version_landing_below_the_newest_never_disturbs_current() {
427 let mut rows = RowMap::default();
430 rows.insert(key("a"), CommitVersion(5), val("five"));
431 let current_after_newest = rows.current_bytes();
432 rows.insert(key("a"), CommitVersion(2), val("two"));
433
434 assert_eq!(rows.current_bytes(), current_after_newest, "a late older version is not current");
435 assert_eq!(walked_version_count(&rows), 2);
436 assert_eq!(rows.bytes(), walked_bytes(&rows));
437 }
438
439 #[test]
440 fn rewriting_a_version_in_place_rebills_it_rather_than_stacking() {
441 let mut rows = RowMap::default();
444 rows.insert(key("a"), CommitVersion(1), val("short"));
445 rows.insert(key("a"), CommitVersion(1), val("a much longer value"));
446
447 assert_eq!(walked_version_count(&rows), 1, "a rewrite is not a new version");
448 assert_eq!(rows.current_bytes(), walked_current_bytes(&rows));
449 assert_eq!(rows.bytes(), walked_bytes(&rows));
450 }
451
452 #[test]
453 fn rewriting_a_historical_version_in_place_rebills_only_history() {
454 let mut rows = RowMap::default();
457 rows.insert(key("a"), CommitVersion(2), val("newest"));
458 let current = rows.current_bytes();
459 rows.insert(key("a"), CommitVersion(1), val("old"));
460 rows.insert(key("a"), CommitVersion(1), val("an old value made longer"));
461
462 assert_eq!(rows.current_bytes(), current, "the newest version was never touched");
463 assert_eq!(walked_version_count(&rows), 2);
464 assert_eq!(rows.bytes(), walked_bytes(&rows));
465 }
466
467 #[test]
468 fn the_tally_matches_a_full_walk_across_mixed_mutations() {
469 let mut rows = RowMap::default();
472 rows.insert(key("a"), CommitVersion(1), val("a1"));
473 rows.insert(key("b"), CommitVersion(1), val("b1"));
474 rows.insert(key("a"), CommitVersion(3), val("a3-longer"));
475 rows.insert(key("a"), CommitVersion(2), val("a2"));
476 rows.insert(key("b"), CommitVersion(4), val("b4"));
477 rows.insert(key("c"), CommitVersion(2), None);
478 rows.insert(key("b"), CommitVersion(4), val("b4-rewritten"));
479
480 assert_eq!(rows.bytes(), walked_bytes(&rows));
481 assert_eq!(rows.current_bytes(), walked_current_bytes(&rows));
482 assert_eq!(rows.historical_bytes(), walked_bytes(&rows) - walked_current_bytes(&rows));
483 assert_eq!(rows.key_count(), 3);
484 assert_eq!(walked_version_count(&rows), 6);
485 }
486
487 #[test]
488 fn a_tombstone_is_a_version_like_any_other() {
489 let mut rows = RowMap::default();
492 rows.insert(key("a"), CommitVersion(1), val("live"));
493 rows.insert(key("a"), CommitVersion(2), None);
494
495 assert_eq!(walked_version_count(&rows), 2);
496 assert_eq!(rows.get(b"a", CommitVersion(2)), Some((CommitVersion(2), &None)));
497 assert_eq!(rows.bytes(), walked_bytes(&rows));
498 }
499
500 #[test]
501 fn a_read_sees_the_newest_version_at_or_below_the_asked_one() {
502 let mut rows = RowMap::default();
505 rows.insert(key("a"), CommitVersion(1), val("one"));
506 rows.insert(key("a"), CommitVersion(3), val("three"));
507 rows.insert(key("a"), CommitVersion(5), val("five"));
508
509 assert_eq!(rows.get(b"a", CommitVersion(4)), Some((CommitVersion(3), &val("three"))));
510 assert_eq!(rows.get(b"a", CommitVersion(5)), Some((CommitVersion(5), &val("five"))));
511 assert_eq!(rows.get(b"a", CommitVersion(2)), Some((CommitVersion(1), &val("one"))));
512 assert_eq!(rows.get(b"a", CommitVersion(0)), None, "nothing was written at or below version 0");
513 assert_eq!(rows.get(b"missing", CommitVersion(5)), None);
514 }
515
516 #[test]
517 fn the_oldest_version_is_the_smallest_whatever_the_insertion_order() {
518 let mut rows = RowMap::default();
521 rows.insert(key("a"), CommitVersion(7), val("seven"));
522 rows.insert(key("b"), CommitVersion(2), val("two"));
523 rows.insert(key("c"), CommitVersion(9), val("nine"));
524 rows.insert(key("d"), CommitVersion(4), val("four"));
525
526 assert_eq!(rows.min_version(), Some(CommitVersion(2)));
527 }
528
529 #[test]
530 fn an_empty_row_map_has_no_oldest_version() {
531 let rows = RowMap::default();
534
535 assert_eq!(rows.min_version(), None);
536 assert!(rows.is_empty());
537 assert_eq!(rows.bytes(), 0);
538 }
539
540 #[test]
541 fn closing_records_the_oldest_version_of_the_rows_it_holds() {
542 let mut active = ActiveRows::new();
545 active.insert(key("a"), CommitVersion(3), val("three"));
546 active.insert(key("b"), CommitVersion(8), val("eight"));
547
548 let closed = active.close();
549
550 assert_eq!(closed.min_version(), CommitVersion(3));
551 }
552
553 #[test]
554 fn range_walks_keys_in_order_within_the_bounds() {
555 let mut rows = RowMap::default();
558 for name in ["a", "b", "c", "d"] {
559 rows.insert(key(name), CommitVersion(1), val(name));
560 }
561
562 let seen: Vec<&[u8]> = rows
563 .range::<(Bound<&[u8]>, Bound<&[u8]>)>((
564 Bound::Excluded(b"a".as_slice()),
565 Bound::Included(b"c".as_slice()),
566 ))
567 .map(|(key, _)| key.as_slice())
568 .collect();
569
570 assert_eq!(seen, vec![b"b".as_slice(), b"c".as_slice()]);
571 }
572
573 #[test]
574 fn removing_the_newest_version_promotes_the_next_one_into_current() {
575 let mut rows = RowMap::default();
579 rows.insert(key("a"), CommitVersion(1), val("one"));
580 rows.insert(key("a"), CommitVersion(2), val("two-two"));
581 rows.insert(key("a"), CommitVersion(3), val("three"));
582
583 let removed = rows.remove(&HashMap::from([(key("a"), HashSet::from([CommitVersion(3)]))]));
584
585 assert_eq!(removed.len(), 1);
586 assert_eq!(removed[0].version, CommitVersion(3));
587 assert_eq!(removed[0].value, val("three"));
588 assert_eq!(walked_version_count(&rows), 2);
589 assert_eq!(rows.current_bytes(), walked_current_bytes(&rows));
590 assert_eq!(rows.bytes(), walked_bytes(&rows));
591 assert_eq!(rows.max_version(), Some(CommitVersion(2)));
592 assert_eq!(rows.get(b"a", CommitVersion(9)), Some((CommitVersion(2), &val("two-two"))));
593 }
594
595 #[test]
596 fn removing_a_historical_version_touches_only_the_historical_tally() {
597 let mut rows = RowMap::default();
600 rows.insert(key("a"), CommitVersion(1), val("one"));
601 rows.insert(key("a"), CommitVersion(2), val("two"));
602 let current = rows.current_bytes();
603
604 rows.remove(&HashMap::from([(key("a"), HashSet::from([CommitVersion(1)]))]));
605
606 assert_eq!(rows.current_bytes(), current);
607 assert_eq!(rows.historical_bytes(), 0);
608 assert_eq!(rows.min_version(), Some(CommitVersion(2)));
609 assert_eq!(walked_version_count(&rows), 1);
610 }
611
612 #[test]
613 fn removing_every_version_of_a_key_forgets_the_key_and_its_versions() {
614 let mut rows = RowMap::default();
617 rows.insert(key("a"), CommitVersion(4), val("four"));
618 rows.insert(key("b"), CommitVersion(6), val("six"));
619
620 let removed = rows.remove(&HashMap::from([(key("a"), HashSet::from([CommitVersion(4)]))]));
621
622 assert_eq!(removed.len(), 1);
623 assert_eq!(rows.key_count(), 1);
624 assert_eq!(rows.min_version(), Some(CommitVersion(6)));
625 assert_eq!(rows.max_version(), Some(CommitVersion(6)));
626 assert_eq!(rows.bytes(), walked_bytes(&rows));
627
628 rows.remove(&HashMap::from([(key("b"), HashSet::from([CommitVersion(6)]))]));
629
630 assert!(rows.is_empty());
631 assert_eq!(rows.min_version(), None);
632 assert_eq!(rows.bytes(), 0);
633 }
634
635 #[test]
636 fn removing_a_version_the_map_does_not_hold_changes_nothing() {
637 let mut rows = RowMap::default();
640 rows.insert(key("a"), CommitVersion(2), val("two"));
641 let bytes = rows.bytes();
642
643 let removed = rows.remove(&HashMap::from([
644 (key("a"), HashSet::from([CommitVersion(1)])),
645 (key("zz"), HashSet::from([CommitVersion(2)])),
646 ]));
647
648 assert!(removed.is_empty());
649 assert_eq!(rows.bytes(), bytes);
650 assert_eq!(walked_version_count(&rows), 1);
651 assert_eq!(rows.min_version(), Some(CommitVersion(2)));
652 }
653
654 #[test]
655 fn a_version_shared_by_two_keys_stays_in_range_until_both_are_gone() {
656 let mut rows = RowMap::default();
659 rows.insert(key("a"), CommitVersion(1), val("a"));
660 rows.insert(key("b"), CommitVersion(1), val("b"));
661 rows.insert(key("c"), CommitVersion(2), val("c"));
662
663 rows.remove(&HashMap::from([(key("a"), HashSet::from([CommitVersion(1)]))]));
664 assert_eq!(rows.min_version(), Some(CommitVersion(1)));
665
666 rows.remove(&HashMap::from([(key("b"), HashSet::from([CommitVersion(1)]))]));
667 assert_eq!(rows.min_version(), Some(CommitVersion(2)));
668 }
669
670 #[test]
671 fn merging_yields_each_key_once_in_order_with_every_map_that_holds_it() {
672 let mut a = RowMap::default();
675 let mut b = RowMap::default();
676 let mut c = RowMap::default();
677 for name in ["a", "c", "e"] {
678 a.insert(key(name), CommitVersion(1), val(name));
679 }
680 for name in ["b", "c"] {
681 b.insert(key(name), CommitVersion(2), val(name));
682 }
683 for name in ["c", "d"] {
684 c.insert(key(name), CommitVersion(3), val(name));
685 }
686 let maps = [&a, &b, &c];
687 let unbounded = || (Bound::<&[u8]>::Unbounded, Bound::<&[u8]>::Unbounded);
688
689 let mut forward = MergedRows::new(maps.iter().map(|rows| rows.range(unbounded())).collect(), false);
690 let mut seen = Vec::new();
691 while let Some((key, group)) = forward.next_group() {
692 seen.push((key.as_slice().to_vec(), group.len()));
693 }
694 assert_eq!(
695 seen,
696 vec![
697 (b"a".to_vec(), 1),
698 (b"b".to_vec(), 1),
699 (b"c".to_vec(), 3),
700 (b"d".to_vec(), 1),
701 (b"e".to_vec(), 1)
702 ]
703 );
704
705 let mut backward =
706 MergedRows::new(maps.iter().map(|rows| rows.range(unbounded()).rev()).collect(), true);
707 let mut seen = Vec::new();
708 while let Some((key, group)) = backward.next_group() {
709 seen.push((key.as_slice().to_vec(), group.len()));
710 }
711 assert_eq!(
712 seen,
713 vec![
714 (b"e".to_vec(), 1),
715 (b"d".to_vec(), 1),
716 (b"c".to_vec(), 3),
717 (b"b".to_vec(), 1),
718 (b"a".to_vec(), 1)
719 ]
720 );
721 }
722}