1use crate::deepsize::{Context, DeepSizeOf};
50use crate::utils::address::RowAddress;
51use crate::{Error, Result};
52use roaring::{RoaringBitmap, RoaringTreemap};
53use std::collections::{HashMap, HashSet};
54use std::hash::{BuildHasherDefault, Hasher};
55use std::mem::size_of;
56
57#[derive(Clone, Debug, PartialEq, Eq)]
64pub enum RowAddrRemap {
65 Compact(CompactRowAddrRemap),
68 Direct(HashMap<u64, Option<u64>>),
70}
71
72impl RowAddrRemap {
73 pub fn compact(groups: impl IntoIterator<Item = GroupInput>) -> Result<Self> {
74 Ok(Self::Compact(CompactRowAddrRemap::new(groups)?))
75 }
76
77 #[doc(hidden)]
80 pub fn compact_with_layout(
81 groups: impl IntoIterator<Item = GroupInputWithLayout>,
82 ) -> Result<Self> {
83 Ok(Self::Compact(CompactRowAddrRemap::new_with_layout(groups)?))
84 }
85
86 pub fn direct(map: HashMap<u64, Option<u64>>) -> Self {
88 Self::Direct(map)
89 }
90
91 pub fn chained(remaps: impl IntoIterator<Item = Self>) -> Self {
94 let mut remaps = remaps
95 .into_iter()
96 .filter(|remap| !remap.is_empty())
97 .collect::<Vec<_>>();
98 match remaps.len() {
99 0 => Self::empty(),
100 1 => remaps.pop().unwrap(),
101 _ => Self::Compact(CompactRowAddrRemap::chained(remaps)),
102 }
103 }
104
105 pub fn empty() -> Self {
107 Self::Direct(HashMap::new())
108 }
109
110 #[inline]
112 pub fn get(&self, addr: u64) -> Option<Option<u64>> {
113 match self {
114 Self::Compact(c) => c.get(addr),
115 Self::Direct(m) => m.get(&addr).copied(),
116 }
117 }
118
119 pub fn remap_in_place(&self, row_addrs: &mut [Option<u64>]) {
126 match self {
127 Self::Compact(compact) => compact.remap_in_place(row_addrs),
128 Self::Direct(_) => {
129 for row_addr in row_addrs {
130 if let Some(addr) = *row_addr
131 && let Some(mapped) = self.get(addr)
132 {
133 *row_addr = mapped;
134 }
135 }
136 }
137 }
138 }
139
140 pub fn is_empty(&self) -> bool {
141 match self {
142 Self::Compact(c) => c.is_empty(),
143 Self::Direct(m) => m.is_empty(),
144 }
145 }
146
147 pub fn affected_fragments(&self) -> RoaringBitmap {
148 match self {
149 Self::Compact(c) => c.affected_fragments(),
150 Self::Direct(m) => RoaringBitmap::from_iter(m.keys().map(|addr| (addr >> 32) as u32)),
151 }
152 }
153
154 pub fn fully_deleted_fragments(&self) -> Option<RoaringBitmap> {
159 match self {
160 Self::Compact(c) => c.fully_deleted_fragments(),
161 Self::Direct(m) if m.is_empty() => Some(RoaringBitmap::new()),
162 Self::Direct(_) => None,
163 }
164 }
165}
166
167impl DeepSizeOf for RowAddrRemap {
168 fn deep_size_of_children(&self, context: &mut Context) -> usize {
169 match self {
170 Self::Compact(compact) => compact.deep_size_of_children(context),
171 Self::Direct(map) => map.deep_size_of_children(context),
172 }
173 }
174}
175
176pub struct GroupInput {
179 pub rewritten_old_row_addrs: RoaringTreemap,
181 pub old_frag_ids: Vec<u32>,
183 pub new_frags: Vec<(u32, u32)>,
185}
186
187#[doc(hidden)]
189pub struct GroupInputWithLayout {
190 pub rewritten_old_row_addrs: RoaringTreemap,
191 pub old_frags: Vec<(u32, u32)>,
192 pub new_frags: Vec<(u32, u32)>,
193}
194
195const ROARING_SIZE_ADVANTAGE_FOR_RANK: usize = 4;
201
202#[derive(Clone, Debug, PartialEq, Eq)]
203enum RankedOffsets {
204 Roaring(RoaringBitmap),
206 Sparse(Vec<u32>),
209 Dense(DenseRankedOffsets),
211}
212
213impl RankedOffsets {
214 fn try_new(offsets: RoaringBitmap, physical_rows: Option<u32>) -> Result<Self> {
215 let universe_rows = physical_rows.map(u64::from).unwrap_or_else(|| {
216 offsets
217 .max()
218 .map(|offset| u64::from(offset) + 1)
219 .unwrap_or(0)
220 });
221 let word_count = usize::try_from(universe_rows.div_ceil(64)).map_err(|_| {
222 Error::invalid_input(format!(
223 "fragment row range {universe_rows} is too large for compact rank lookup"
224 ))
225 })?;
226 let sparse_bytes = usize::try_from(offsets.len())
227 .ok()
228 .and_then(|len| len.checked_mul(size_of::<u32>()))
229 .ok_or_else(|| {
230 Error::invalid_input(format!(
231 "rewritten row count {} is too large for sparse rank lookup",
232 offsets.len()
233 ))
234 })?;
235 let dense_bytes = word_count
236 .checked_mul(size_of::<u64>() + size_of::<u32>())
237 .ok_or_else(|| {
238 Error::invalid_input(format!(
239 "fragment row range {universe_rows} is too large for dense rank lookup"
240 ))
241 })?;
242 let rank_friendly_bytes = sparse_bytes.min(dense_bytes);
243 if offsets
244 .serialized_size()
245 .checked_mul(ROARING_SIZE_ADVANTAGE_FOR_RANK)
246 .is_some_and(|roaring_bytes| roaring_bytes < rank_friendly_bytes)
247 {
248 return Ok(Self::Roaring(offsets));
249 }
250 if sparse_bytes <= dense_bytes {
251 return Ok(Self::Sparse(offsets.into_iter().collect()));
252 }
253 Ok(Self::Dense(DenseRankedOffsets::try_new(
254 offsets, word_count,
255 )?))
256 }
257
258 #[inline]
260 fn rank_if_present(&self, offset: u32) -> Option<u64> {
261 match self {
262 Self::Roaring(offsets) => offsets.contains(offset).then(|| offsets.rank(offset) - 1),
263 Self::Sparse(offsets) => offsets.binary_search(&offset).ok().map(|rank| rank as u64),
264 Self::Dense(offsets) => offsets.rank_if_present(offset),
265 }
266 }
267
268 fn is_empty(&self) -> bool {
269 match self {
270 Self::Roaring(offsets) => offsets.is_empty(),
271 Self::Sparse(offsets) => offsets.is_empty(),
272 Self::Dense(offsets) => offsets.words.is_empty(),
273 }
274 }
275}
276
277impl DeepSizeOf for RankedOffsets {
278 fn deep_size_of_children(&self, context: &mut Context) -> usize {
279 match self {
280 Self::Roaring(offsets) => offsets.serialized_size(),
283 Self::Sparse(offsets) => offsets.deep_size_of_children(context),
284 Self::Dense(offsets) => offsets.deep_size_of_children(context),
285 }
286 }
287}
288
289#[derive(Clone, Debug, PartialEq, Eq)]
290struct DenseRankedOffsets {
291 words: Vec<u64>,
292 rank_before_word: Vec<u32>,
293}
294
295impl DenseRankedOffsets {
296 fn try_new(offsets: RoaringBitmap, word_count: usize) -> Result<Self> {
297 let mut words = vec![0u64; word_count];
298 for offset in offsets {
299 let word_idx = (offset / 64) as usize;
300 let Some(word) = words.get_mut(word_idx) else {
301 return Err(Error::invalid_input(format!(
302 "rewritten row offset {offset} is outside dense rank word_count={word_count}"
303 )));
304 };
305 *word |= 1u64 << (offset % 64);
306 }
307
308 let mut rank_before_word = Vec::with_capacity(word_count);
309 let mut rewritten_rows_before = 0u64;
310 for word in &words {
311 rank_before_word.push(u32::try_from(rewritten_rows_before).map_err(|_| {
312 Error::invalid_input(format!(
313 "rewritten row count {rewritten_rows_before} exceeds the row-address offset range"
314 ))
315 })?);
316 rewritten_rows_before += u64::from(word.count_ones());
317 }
318 Ok(Self {
319 words,
320 rank_before_word,
321 })
322 }
323
324 #[inline]
325 fn rank_if_present(&self, offset: u32) -> Option<u64> {
326 let word_idx = (offset / 64) as usize;
327 let word = *self.words.get(word_idx)?;
328 let bit = 1u64 << (offset % 64);
329 if word & bit == 0 {
330 return None;
331 }
332 Some(
333 u64::from(self.rank_before_word[word_idx]) + u64::from((word & (bit - 1)).count_ones()),
334 )
335 }
336}
337
338impl DeepSizeOf for DenseRankedOffsets {
339 fn deep_size_of_children(&self, context: &mut Context) -> usize {
340 self.words.deep_size_of_children(context)
341 + self.rank_before_word.deep_size_of_children(context)
342 }
343}
344
345#[derive(Clone, Debug, PartialEq, Eq)]
346struct OldFragmentRemap {
347 group_idx: usize,
348 rewritten_offsets: RankedOffsets,
349 rewritten_rows_before: u64,
350 physical_rows: Option<u32>,
351}
352
353impl DeepSizeOf for OldFragmentRemap {
354 fn deep_size_of_children(&self, context: &mut Context) -> usize {
355 self.rewritten_offsets.deep_size_of_children(context)
356 }
357}
358
359#[derive(Clone, Debug, PartialEq, Eq)]
360struct GroupRemap {
361 new_frag_row_ranges: Vec<(u32, u64, u32)>,
364}
365
366impl GroupRemap {
367 fn new(input: GroupInput, group_idx: usize) -> Result<(Self, Vec<(u32, OldFragmentRemap)>)> {
368 Self::new_with_old_frags(
369 input.rewritten_old_row_addrs,
370 input.old_frag_ids.into_iter().map(|id| (id, None)),
371 input.new_frags,
372 group_idx,
373 )
374 }
375
376 fn new_with_layout(
377 input: GroupInputWithLayout,
378 group_idx: usize,
379 ) -> Result<(Self, Vec<(u32, OldFragmentRemap)>)> {
380 Self::new_with_old_frags(
381 input.rewritten_old_row_addrs,
382 input
383 .old_frags
384 .into_iter()
385 .map(|(id, rows)| (id, Some(rows))),
386 input.new_frags,
387 group_idx,
388 )
389 }
390
391 fn new_with_old_frags(
392 rewritten_old_row_addrs: RoaringTreemap,
393 old_frags: impl IntoIterator<Item = (u32, Option<u32>)>,
394 new_frags: Vec<(u32, u32)>,
395 group_idx: usize,
396 ) -> Result<(Self, Vec<(u32, OldFragmentRemap)>)> {
397 let mut new_frag_row_ranges = Vec::with_capacity(new_frags.len());
400 let mut rewritten_rows_before = 0u64;
401 for (frag_id, physical_rows) in new_frags {
402 if physical_rows == 0 {
403 continue;
404 }
405 new_frag_row_ranges.push((frag_id, rewritten_rows_before, physical_rows));
406 rewritten_rows_before += physical_rows as u64;
407 }
408 let total_new_rows = rewritten_rows_before;
409
410 let mut per_frag: IntMap<u32, RoaringBitmap> = rewritten_old_row_addrs
411 .bitmaps()
412 .map(|(frag_id, bitmap)| (frag_id, bitmap.clone()))
413 .collect();
414 let old_frags = old_frags.into_iter().collect::<Vec<_>>();
415 let mut frags = Vec::with_capacity(old_frags.len());
416 let mut seen_frag_ids = HashSet::with_capacity(old_frags.len());
417 let mut rewritten_rows_before = 0u64;
418 for &(frag_id, physical_rows) in &old_frags {
419 if !seen_frag_ids.insert(frag_id) {
420 return Err(Error::invalid_input(format!(
421 "rewrite group {group_idx} contains old fragment {frag_id} more than once"
422 )));
423 }
424 let bitmap = per_frag.remove(&frag_id).unwrap_or_default();
425 if let Some(physical_rows) = physical_rows
426 && bitmap.max().is_some_and(|offset| offset >= physical_rows)
427 {
428 return Err(Error::invalid_input(format!(
429 "rewrite group {group_idx} contains a row offset outside old fragment {frag_id} with physical_rows={physical_rows}"
430 )));
431 }
432 let num_rewritten_rows = bitmap.len();
433 let rewritten_offsets = RankedOffsets::try_new(bitmap, physical_rows)?;
434 frags.push((
435 frag_id,
436 OldFragmentRemap {
437 group_idx,
438 rewritten_offsets,
439 rewritten_rows_before,
440 physical_rows,
441 },
442 ));
443 rewritten_rows_before += num_rewritten_rows;
444 }
445 if !per_frag.is_empty() {
447 return Err(Error::invalid_input(format!(
448 "compaction rewrite group {group_idx} references rewritten old row addresses from fragments {:?} not in its old fragments {:?}",
449 per_frag.keys().collect::<Vec<_>>(),
450 old_frags,
451 )));
452 }
453
454 let total_rewritten_old_rows = rewritten_old_row_addrs.len();
457 if total_new_rows != total_rewritten_old_rows {
458 return Err(Error::invalid_input(format!(
459 "compaction rewrite group {group_idx} rewrote {total_rewritten_old_rows} old rows from fragments {:?} but the new fragments hold {total_new_rows} rows",
460 old_frags,
461 )));
462 }
463
464 Ok((
465 Self {
466 new_frag_row_ranges,
467 },
468 frags,
469 ))
470 }
471
472 fn compute_new_addr(&self, rewritten_row_index: u64) -> u64 {
473 let idx =
474 match self
475 .new_frag_row_ranges
476 .binary_search_by(|(_, rewritten_rows_before, _)| {
477 rewritten_rows_before.cmp(&rewritten_row_index)
478 }) {
479 Ok(i) => i,
480 Err(i) => i - 1,
481 };
482 let (frag_id, rewritten_rows_before, _rows) = self.new_frag_row_ranges[idx];
483 let offset = (rewritten_row_index - rewritten_rows_before) as u32;
484 u64::from(RowAddress::new_from_parts(frag_id, offset))
485 }
486}
487
488impl DeepSizeOf for GroupRemap {
489 fn deep_size_of_children(&self, context: &mut Context) -> usize {
490 self.new_frag_row_ranges.deep_size_of_children(context)
491 }
492}
493
494#[derive(Clone, Copy)]
506struct IntHasher(u64);
507
508const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
510
511impl Default for IntHasher {
512 #[inline]
513 fn default() -> Self {
514 Self(FNV_OFFSET_BASIS)
515 }
516}
517
518impl Hasher for IntHasher {
519 #[inline]
524 fn finish(&self) -> u64 {
525 self.0 ^ (self.0 >> 32)
526 }
527
528 #[inline]
529 fn write(&mut self, bytes: &[u8]) {
530 for &b in bytes {
532 self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3);
533 }
534 }
535
536 #[inline]
537 fn write_u32(&mut self, value: u32) {
538 self.0 = u64::from(value).wrapping_mul(0x9E37_79B9_7F4A_7C15);
539 }
540
541 #[inline]
542 fn write_u64(&mut self, value: u64) {
543 self.0 = value.wrapping_mul(0x9E37_79B9_7F4A_7C15);
544 }
545
546 #[inline]
547 fn write_usize(&mut self, value: usize) {
548 self.write_u64(value as u64);
549 }
550}
551
552type IntMap<K, V> = HashMap<K, V, BuildHasherDefault<IntHasher>>;
554
555#[derive(Clone, Debug, PartialEq, Eq)]
556struct CompactRemapStep {
557 groups: Vec<GroupRemap>,
558 frags: IntMap<u32, OldFragmentRemap>,
561}
562
563impl CompactRemapStep {
564 fn new(groups: impl IntoIterator<Item = GroupInput>) -> Result<Self> {
565 let mut frags = IntMap::default();
566 let mut group_remaps = Vec::new();
567 for input in groups {
568 let gi = group_remaps.len();
569 let (group_remap, group_frags) = GroupRemap::new(input, gi)?;
570 for (frag_id, frag) in group_frags {
571 if frags.insert(frag_id, frag).is_some() {
572 return Err(Error::invalid_input(format!(
573 "old fragment {frag_id} appears in more than one rewrite group, including group {gi}"
574 )));
575 }
576 }
577 group_remaps.push(group_remap);
578 }
579 Ok(Self {
580 groups: group_remaps,
581 frags,
582 })
583 }
584
585 fn new_with_layout(groups: impl IntoIterator<Item = GroupInputWithLayout>) -> Result<Self> {
586 let mut frags = IntMap::default();
587 let mut group_remaps = Vec::new();
588 for input in groups {
589 let gi = group_remaps.len();
590 let (group_remap, group_frags) = GroupRemap::new_with_layout(input, gi)?;
591 for (frag_id, frag) in group_frags {
592 if frags.insert(frag_id, frag).is_some() {
593 return Err(Error::invalid_input(format!(
594 "old fragment {frag_id} appears in more than one rewrite group, including group {gi}"
595 )));
596 }
597 }
598 group_remaps.push(group_remap);
599 }
600 Ok(Self {
601 groups: group_remaps,
602 frags,
603 })
604 }
605
606 #[inline]
607 pub fn get(&self, addr: u64) -> Option<Option<u64>> {
608 let frag = (addr >> 32) as u32;
609 let old_frag = self.frags.get(&frag)?;
611 let offset = addr as u32;
612 if old_frag
613 .physical_rows
614 .is_some_and(|physical_rows| offset >= physical_rows)
615 {
616 return None;
617 }
618 let Some(rewritten_rank) = old_frag.rewritten_offsets.rank_if_present(offset) else {
619 return Some(None);
620 };
621 let rewritten_row_index = old_frag.rewritten_rows_before + rewritten_rank;
622 Some(Some(
623 self.groups[old_frag.group_idx].compute_new_addr(rewritten_row_index),
624 ))
625 }
626
627 pub fn is_empty(&self) -> bool {
628 self.groups.is_empty()
629 }
630
631 fn fully_deleted_fragments(&self) -> Option<RoaringBitmap> {
632 if self
634 .frags
635 .values()
636 .any(|frag| !frag.rewritten_offsets.is_empty())
637 {
638 return None;
639 }
640 Some(RoaringBitmap::from_iter(self.frags.keys().copied()))
641 }
642
643 fn affected_fragments(&self) -> RoaringBitmap {
644 RoaringBitmap::from_iter(self.frags.keys().copied())
645 }
646}
647
648impl DeepSizeOf for CompactRemapStep {
649 fn deep_size_of_children(&self, context: &mut Context) -> usize {
650 self.groups.deep_size_of_children(context) + self.frags.deep_size_of_children(context)
651 }
652}
653
654#[derive(Clone, Debug, PartialEq, Eq)]
655enum RemapStep {
656 Compact(CompactRemapStep),
657 Direct(HashMap<u64, Option<u64>>),
658}
659
660impl RemapStep {
661 fn get(&self, addr: u64) -> Option<Option<u64>> {
662 match self {
663 Self::Compact(compact) => compact.get(addr),
664 Self::Direct(direct) => direct.get(&addr).copied(),
665 }
666 }
667
668 fn is_empty(&self) -> bool {
669 match self {
670 Self::Compact(compact) => compact.is_empty(),
671 Self::Direct(direct) => direct.is_empty(),
672 }
673 }
674
675 fn affected_fragments(&self) -> RoaringBitmap {
676 match self {
677 Self::Compact(compact) => compact.affected_fragments(),
678 Self::Direct(direct) => {
679 RoaringBitmap::from_iter(direct.keys().map(|addr| (addr >> 32) as u32))
680 }
681 }
682 }
683
684 fn fully_deleted_fragments(&self) -> Option<RoaringBitmap> {
685 match self {
686 Self::Compact(compact) => compact.fully_deleted_fragments(),
687 Self::Direct(direct) if direct.is_empty() => Some(RoaringBitmap::new()),
688 Self::Direct(_) => None,
689 }
690 }
691}
692
693impl DeepSizeOf for RemapStep {
694 fn deep_size_of_children(&self, context: &mut Context) -> usize {
695 match self {
696 Self::Compact(compact) => compact.deep_size_of_children(context),
697 Self::Direct(direct) => direct.deep_size_of_children(context),
698 }
699 }
700}
701
702#[derive(Clone, Debug, PartialEq, Eq)]
707pub struct CompactRowAddrRemap {
708 steps: Vec<RemapStep>,
709}
710
711impl CompactRowAddrRemap {
712 fn new(groups: impl IntoIterator<Item = GroupInput>) -> Result<Self> {
713 Ok(Self {
714 steps: vec![RemapStep::Compact(CompactRemapStep::new(groups)?)],
715 })
716 }
717
718 fn new_with_layout(groups: impl IntoIterator<Item = GroupInputWithLayout>) -> Result<Self> {
719 Ok(Self {
720 steps: vec![RemapStep::Compact(CompactRemapStep::new_with_layout(
721 groups,
722 )?)],
723 })
724 }
725
726 fn chained(remaps: Vec<RowAddrRemap>) -> Self {
727 let mut steps = Vec::with_capacity(remaps.len());
728 for remap in remaps {
729 match remap {
730 RowAddrRemap::Compact(compact) => steps.extend(compact.steps),
731 RowAddrRemap::Direct(direct) => steps.push(RemapStep::Direct(direct)),
732 }
733 }
734 Self { steps }
735 }
736
737 #[inline]
738 pub fn get(&self, addr: u64) -> Option<Option<u64>> {
739 let mut current = addr;
740 let mut was_affected = false;
741 for step in &self.steps {
742 match step.get(current) {
743 None => {}
744 Some(None) => return Some(None),
745 Some(Some(mapped)) => {
746 current = mapped;
747 was_affected = true;
748 }
749 }
750 }
751 was_affected.then_some(Some(current))
752 }
753
754 fn remap_in_place(&self, row_addrs: &mut [Option<u64>]) {
755 for step in &self.steps {
756 for row_addr in row_addrs.iter_mut() {
757 if let Some(addr) = *row_addr
758 && let Some(mapped) = step.get(addr)
759 {
760 *row_addr = mapped;
761 }
762 }
763 }
764 }
765
766 pub fn is_empty(&self) -> bool {
767 self.steps.iter().all(RemapStep::is_empty)
768 }
769
770 fn affected_fragments(&self) -> RoaringBitmap {
771 self.steps
772 .iter()
773 .fold(RoaringBitmap::new(), |mut affected, step| {
774 affected |= step.affected_fragments();
775 affected
776 })
777 }
778
779 fn fully_deleted_fragments(&self) -> Option<RoaringBitmap> {
780 self.steps
781 .iter()
782 .try_fold(RoaringBitmap::new(), |mut deleted, step| {
783 deleted |= step.fully_deleted_fragments()?;
784 Some(deleted)
785 })
786 }
787}
788
789impl DeepSizeOf for CompactRowAddrRemap {
790 fn deep_size_of_children(&self, context: &mut Context) -> usize {
791 self.steps.deep_size_of_children(context)
792 }
793}
794
795#[cfg(test)]
796mod tests {
797 use super::*;
798 use proptest::{prop_assert, prop_assert_eq};
799
800 fn addr(frag: u32, offset: u32) -> u64 {
801 u64::from(RowAddress::new_from_parts(frag, offset))
802 }
803
804 #[derive(Clone, Copy)]
805 enum ExpectedRankedOffsets {
806 Sparse,
807 Dense,
808 Roaring,
809 }
810
811 fn assert_layout_matches_legacy(
812 frag_id: u32,
813 physical_rows: u32,
814 rewritten_old_row_addrs: RoaringTreemap,
815 new_frags: Vec<(u32, u32)>,
816 expected_representation: ExpectedRankedOffsets,
817 ) {
818 let rewritten_addrs = rewritten_old_row_addrs.iter().collect::<Vec<_>>();
819 let new_addrs = new_frags
820 .iter()
821 .flat_map(|(new_frag_id, rows)| (0..*rows).map(|offset| addr(*new_frag_id, offset)))
822 .collect::<Vec<_>>();
823 assert_eq!(rewritten_addrs.len(), new_addrs.len());
824 let expected_moved = rewritten_addrs
825 .iter()
826 .copied()
827 .zip(new_addrs)
828 .collect::<HashMap<_, _>>();
829
830 let remap = RowAddrRemap::compact_with_layout([GroupInputWithLayout {
831 rewritten_old_row_addrs,
832 old_frags: vec![(frag_id, physical_rows)],
833 new_frags,
834 }])
835 .unwrap();
836
837 let RowAddrRemap::Compact(compact) = &remap else {
838 panic!("compact_with_layout must produce a compact remap");
839 };
840 let RemapStep::Compact(step) = &compact.steps[0] else {
841 panic!("compact_with_layout must produce a compact step");
842 };
843 let offsets = &step.frags[&frag_id].rewritten_offsets;
844 assert!(match expected_representation {
845 ExpectedRankedOffsets::Sparse => matches!(offsets, RankedOffsets::Sparse(_)),
846 ExpectedRankedOffsets::Dense => matches!(offsets, RankedOffsets::Dense(_)),
847 ExpectedRankedOffsets::Roaring => matches!(offsets, RankedOffsets::Roaring(_)),
848 });
849
850 for offset in 0..physical_rows {
851 let old_addr = addr(frag_id, offset);
852 assert_eq!(
853 remap.get(old_addr),
854 Some(expected_moved.get(&old_addr).copied()),
855 "mismatch at ({frag_id}, {offset})"
856 );
857 }
858 assert_eq!(remap.get(addr(frag_id, physical_rows)), None);
859 assert_eq!(remap.get(addr(frag_id + 1, 0)), None);
860 }
861
862 #[test]
863 fn test_sparse_ranked_offsets() {
864 let offsets = RankedOffsets::try_new(
865 RoaringBitmap::from_iter([1u32, 63, 511, 9_999]),
866 Some(10_000),
867 )
868 .unwrap();
869 assert!(matches!(offsets, RankedOffsets::Sparse(_)));
870 assert_eq!(offsets.rank_if_present(0), None);
871 assert_eq!(offsets.rank_if_present(1), Some(0));
872 assert_eq!(offsets.rank_if_present(63), Some(1));
873 assert_eq!(offsets.rank_if_present(511), Some(2));
874 assert_eq!(offsets.rank_if_present(9_999), Some(3));
875 }
876
877 #[test]
878 fn test_dense_ranked_offsets_across_words() {
879 let rewritten = (0..1_024u32)
880 .filter(|offset| offset % 10 != 0)
881 .collect::<RoaringBitmap>();
882 let offsets = RankedOffsets::try_new(rewritten.clone(), Some(1_024)).unwrap();
883 assert!(matches!(offsets, RankedOffsets::Dense(_)));
884
885 let mut expected_rank = 0u64;
886 for offset in 0..1_024 {
887 if rewritten.contains(offset) {
888 assert_eq!(offsets.rank_if_present(offset), Some(expected_rank));
889 expected_rank += 1;
890 } else {
891 assert_eq!(offsets.rank_if_present(offset), None);
892 }
893 }
894 assert_eq!(expected_rank, rewritten.len());
895 }
896
897 #[test]
898 fn test_run_compressed_ranked_offsets() {
899 let mut rewritten = RoaringBitmap::new();
900 rewritten.insert_range(100..9_900);
901 let offsets = RankedOffsets::try_new(rewritten, Some(10_000)).unwrap();
902 assert!(matches!(offsets, RankedOffsets::Roaring(_)));
903 assert_eq!(offsets.rank_if_present(99), None);
904 assert_eq!(offsets.rank_if_present(100), Some(0));
905 assert_eq!(offsets.rank_if_present(9_899), Some(9_799));
906 assert_eq!(offsets.rank_if_present(9_900), None);
907 }
908
909 #[test]
910 fn test_compact_with_layout_matches_legacy_across_rank_representations() {
911 assert_layout_matches_legacy(
912 1,
913 10_000,
914 RoaringTreemap::from_iter(
915 [1u32, 63, 511, 9_999]
916 .into_iter()
917 .map(|offset| addr(1, offset)),
918 ),
919 vec![(10, 2), (11, 2)],
920 ExpectedRankedOffsets::Sparse,
921 );
922
923 let dense = (0..1_024u32)
924 .filter(|offset| offset % 10 != 0)
925 .map(|offset| addr(2, offset))
926 .collect::<RoaringTreemap>();
927 let dense_rows = u32::try_from(dense.len()).unwrap();
928 assert_layout_matches_legacy(
929 2,
930 1_024,
931 dense,
932 vec![(20, 400), (21, dense_rows - 400)],
933 ExpectedRankedOffsets::Dense,
934 );
935
936 let mut captured = RoaringTreemap::new();
940 captured.insert_range(addr(3, 100)..addr(3, 9_900));
941 let mut serialized = Vec::with_capacity(captured.serialized_size());
942 captured.serialize_into(&mut serialized).unwrap();
943 let persisted = RoaringTreemap::deserialize_from(std::io::Cursor::new(serialized)).unwrap();
944 assert_layout_matches_legacy(
945 3,
946 10_000,
947 persisted,
948 vec![(31, 5_000), (30, 4_800)],
949 ExpectedRankedOffsets::Roaring,
950 );
951 }
952
953 #[test]
954 fn test_compact_lookup() {
955 let group_a = GroupInput {
960 rewritten_old_row_addrs: RoaringTreemap::from_iter([
961 addr(4, 0),
962 addr(4, 2),
963 addr(4, 4),
964 addr(3, 0),
965 addr(3, 1),
966 ]),
967 old_frag_ids: vec![4, 3],
968 new_frags: vec![(10, 2), (11, 0), (12, 3)],
969 };
970 let group_b = GroupInput {
971 rewritten_old_row_addrs: RoaringTreemap::new(),
972 old_frag_ids: vec![7],
973 new_frags: vec![],
974 };
975 let remap = RowAddrRemap::compact([group_a, group_b]).unwrap();
976
977 assert_eq!(remap.get(addr(4, 0)), Some(Some(addr(10, 0))));
979 assert_eq!(remap.get(addr(4, 2)), Some(Some(addr(10, 1))));
980 assert_eq!(remap.get(addr(4, 4)), Some(Some(addr(12, 0))));
982 assert_eq!(remap.get(addr(3, 0)), Some(Some(addr(12, 1))));
983 assert_eq!(remap.get(addr(3, 1)), Some(Some(addr(12, 2))));
984 assert_eq!(remap.get(addr(4, 1)), Some(None));
986 assert_eq!(remap.get(addr(4, 3)), Some(None));
987 assert_eq!(remap.get(addr(7, 0)), Some(None));
989 assert_eq!(remap.get(addr(9, 0)), None);
991 assert_eq!(remap.get(addr(4, 5)), Some(None));
992 assert!(!remap.is_empty());
993 }
994
995 #[test]
996 fn test_fragment_sets() {
997 let first_dead = RowAddrRemap::compact([GroupInput {
1000 rewritten_old_row_addrs: RoaringTreemap::new(),
1001 old_frag_ids: vec![3],
1002 new_frags: vec![],
1003 }])
1004 .unwrap();
1005 let second_dead = RowAddrRemap::compact([GroupInput {
1006 rewritten_old_row_addrs: RoaringTreemap::new(),
1007 old_frag_ids: vec![7],
1008 new_frags: vec![],
1009 }])
1010 .unwrap();
1011 let dead = RowAddrRemap::chained([first_dead.clone(), second_dead]);
1012 assert_eq!(
1013 dead.fully_deleted_fragments(),
1014 Some(RoaringBitmap::from_iter([3u32, 7u32]))
1015 );
1016 assert_eq!(
1017 dead.affected_fragments(),
1018 RoaringBitmap::from_iter([3u32, 7u32])
1019 );
1020
1021 let alive = RowAddrRemap::compact([GroupInput {
1024 rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(0, 0)]),
1025 old_frag_ids: vec![0, 1],
1026 new_frags: vec![(10, 1)],
1027 }])
1028 .unwrap();
1029 assert!(alive.fully_deleted_fragments().is_none());
1030 assert_eq!(
1031 alive.affected_fragments(),
1032 RoaringBitmap::from_iter([0u32, 1u32])
1033 );
1034 assert!(
1035 RowAddrRemap::chained([first_dead, alive])
1036 .fully_deleted_fragments()
1037 .is_none()
1038 );
1039 }
1040
1041 #[test]
1042 fn test_direct_partial_map_is_not_fully_deleted() {
1043 let remap = RowAddrRemap::direct(HashMap::from([(addr(5, 1), None)]));
1044
1045 assert_eq!(remap.get(addr(5, 0)), None);
1046 assert_eq!(remap.fully_deleted_fragments(), None);
1047 }
1048
1049 #[test]
1050 fn test_chained_partial_direct_maps_are_not_fully_deleted() {
1051 let remap = RowAddrRemap::chained([
1052 RowAddrRemap::direct(HashMap::from([(addr(5, 1), None)])),
1053 RowAddrRemap::direct(HashMap::from([(addr(6, 1), None)])),
1054 ]);
1055
1056 assert!(matches!(remap, RowAddrRemap::Compact(_)));
1057 assert_eq!(remap.get(addr(5, 0)), None);
1058 assert_eq!(remap.get(addr(6, 0)), None);
1059 assert_eq!(remap.fully_deleted_fragments(), None);
1060 }
1061
1062 #[test]
1063 fn test_compact_rejects_rewritten_addrs_outside_old_frags() {
1064 let input = GroupInput {
1067 rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(0, 0), addr(5, 0)]),
1068 old_frag_ids: vec![0],
1069 new_frags: vec![(10, 2)],
1070 };
1071 assert!(RowAddrRemap::compact([input]).is_err());
1072 }
1073
1074 #[test]
1075 fn test_compact_preserves_explicit_fragment_order() {
1076 let remap = RowAddrRemap::compact([GroupInput {
1077 rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(0, 0), addr(0, 1)]),
1078 old_frag_ids: vec![0],
1079 new_frags: vec![(12, 1), (11, 1)],
1080 }])
1081 .unwrap();
1082 assert_eq!(remap.get(addr(0, 0)), Some(Some(addr(12, 0))));
1083 assert_eq!(remap.get(addr(0, 1)), Some(Some(addr(11, 0))));
1084 }
1085
1086 #[test]
1087 fn test_direct_and_empty() {
1088 let mut map = HashMap::new();
1090 map.insert(addr(2, 0), Some(addr(9, 9)));
1091 map.insert(addr(5, 1), None);
1092 let remap = RowAddrRemap::direct(map);
1093 assert_eq!(remap.get(addr(2, 0)), Some(Some(addr(9, 9))));
1094 assert_eq!(remap.get(addr(5, 1)), Some(None));
1095 assert_eq!(remap.get(addr(2, 1)), None);
1096 assert_eq!(
1098 remap.affected_fragments(),
1099 RoaringBitmap::from_iter([2u32, 5u32])
1100 );
1101
1102 let empty = RowAddrRemap::empty();
1103 assert!(empty.is_empty());
1104 assert_eq!(empty.get(addr(0, 0)), None);
1105 }
1106
1107 #[test]
1108 fn test_chained_lookup_and_batch() {
1109 let first = RowAddrRemap::compact([GroupInput {
1110 rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(0, 0), addr(0, 2)]),
1111 old_frag_ids: vec![0],
1112 new_frags: vec![(10, 2)],
1113 }])
1114 .unwrap();
1115 let second = RowAddrRemap::compact([GroupInput {
1116 rewritten_old_row_addrs: RoaringTreemap::from_iter([addr(10, 1)]),
1117 old_frag_ids: vec![10],
1118 new_frags: vec![(20, 1)],
1119 }])
1120 .unwrap();
1121 let chain = RowAddrRemap::chained([first, second]);
1122
1123 assert_eq!(chain.get(addr(0, 0)), Some(None));
1124 assert_eq!(chain.get(addr(0, 1)), Some(None));
1125 assert_eq!(chain.get(addr(0, 2)), Some(Some(addr(20, 0))));
1126 assert_eq!(chain.get(addr(1, 0)), None);
1127
1128 let mut batch = vec![
1129 Some(addr(0, 0)),
1130 Some(addr(0, 1)),
1131 Some(addr(0, 2)),
1132 Some(addr(1, 0)),
1133 None,
1134 ];
1135 chain.remap_in_place(&mut batch);
1136 assert_eq!(
1137 batch,
1138 vec![None, None, Some(addr(20, 0)), Some(addr(1, 0)), None]
1139 );
1140 }
1141
1142 fn int_hash_of<K: std::hash::Hash>(key: &K) -> u64 {
1143 use std::hash::BuildHasher;
1144 BuildHasherDefault::<IntHasher>::default().hash_one(key)
1145 }
1146
1147 #[test]
1148 fn test_int_hasher_is_stateless_and_spreads_low_bits() {
1149 for value in [0u32, 1, 7, 12345, 1 << 31, u32::MAX] {
1152 assert_eq!(int_hash_of(&value), int_hash_of(&value));
1153 }
1154 let key = 424242u32;
1156 assert_eq!(int_hash_of(&key), int_hash_of(&&key));
1157
1158 let buckets = |ids: &[u32]| -> usize {
1162 let table_mask = 2048 - 1;
1163 ids.iter()
1164 .map(|id| int_hash_of(id) & table_mask)
1165 .collect::<std::collections::HashSet<_>>()
1166 .len()
1167 };
1168 let strided: Vec<u32> = (0..1024).map(|i| i << 20).collect();
1169 let dense: Vec<u32> = (0..1024).collect();
1170 assert!(
1171 buckets(&strided) > 512,
1172 "power-of-two-strided fragment ids clustered into {} of 2048 buckets",
1173 buckets(&strided)
1174 );
1175 assert!(buckets(&dense) > 512, "dense fragment ids clustered");
1176 }
1177
1178 #[test]
1179 fn test_int_hasher_composite_keys_collide_on_last_field() {
1180 assert_eq!(int_hash_of(&(1u32, 5u32)), int_hash_of(&(999u32, 5u32)));
1185 assert_eq!(int_hash_of(&("abc", 4u32)), int_hash_of(&("zzzzzz", 4u32)));
1186
1187 let mut map: IntMap<(u32, u32), u32> = IntMap::default();
1188 for a in 0..48u32 {
1189 for b in 0..48u32 {
1190 map.insert((a, b), a * 1000 + b);
1191 }
1192 }
1193 assert_eq!(map.len(), 48 * 48);
1194 for a in 0..48u32 {
1195 for b in 0..48u32 {
1196 assert_eq!(map.get(&(a, b)), Some(&(a * 1000 + b)));
1197 }
1198 }
1199 assert_eq!(map.get(&(48, 0)), None);
1200 }
1201
1202 #[test]
1203 fn test_outputs_do_not_depend_on_fragment_insertion_order() {
1204 let remap_over = |old_frag_ids: Vec<u32>| {
1207 let rewritten = RoaringTreemap::from_iter(old_frag_ids.iter().map(|f| addr(*f, 0)));
1208 let num_rows = old_frag_ids.len() as u32;
1209 RowAddrRemap::compact([GroupInput {
1210 rewritten_old_row_addrs: rewritten,
1211 old_frag_ids,
1212 new_frags: vec![(1000, num_rows)],
1213 }])
1214 .unwrap()
1215 };
1216 let forward = remap_over(vec![3, 1, 4, 1 << 20, 2 << 20, 9, 700_000]);
1217 let reverse = remap_over(vec![700_000, 9, 2 << 20, 1 << 20, 4, 1, 3]);
1218
1219 assert_eq!(
1222 forward.affected_fragments(),
1223 reverse.affected_fragments(),
1224 "affected fragments must not depend on insertion order"
1225 );
1226 assert_eq!(
1227 forward.fully_deleted_fragments(),
1228 reverse.fully_deleted_fragments()
1229 );
1230 }
1231
1232 proptest::proptest! {
1233 #[test]
1237 fn test_compact_matches_direct_over_random_rewrites(
1238 frag_seeds in proptest::collection::vec((0..3usize, 0..64u32), 1..7),
1241 rows_per_frag in 1..24u32,
1242 keep in proptest::collection::vec(proptest::bool::weighted(0.75), 6 * 24),
1243 gaps in proptest::collection::vec(1..4u32, 1..5),
1244 ) {
1245 let mut old_frag_ids: Vec<u32> = Vec::new();
1246 for (shape, seed) in &frag_seeds {
1247 let frag_id = match shape {
1248 0 => *seed,
1249 1 => (*seed + 1) << 20,
1250 _ => seed.wrapping_mul(7919),
1251 };
1252 if !old_frag_ids.contains(&frag_id) {
1253 old_frag_ids.push(frag_id);
1254 }
1255 }
1256
1257 let mut rewritten = RoaringTreemap::new();
1260 let mut read_order: Vec<u64> = Vec::new();
1261 let mut covered: Vec<u64> = Vec::new();
1262 for (frag_index, frag_id) in old_frag_ids.iter().enumerate() {
1263 for offset in 0..rows_per_frag {
1264 covered.push(addr(*frag_id, offset));
1265 if keep[frag_index * rows_per_frag as usize + offset as usize] {
1266 rewritten.insert(addr(*frag_id, offset));
1267 read_order.push(addr(*frag_id, offset));
1268 }
1269 }
1270 }
1271 let total_rewritten = read_order.len() as u32;
1272 proptest::prop_assume!(total_rewritten > 0);
1273
1274 let parts = gaps.len().min(total_rewritten as usize);
1277 let base = total_rewritten / parts as u32;
1278 let remainder = total_rewritten % parts as u32;
1279 let mut new_frags: Vec<(u32, u32)> = Vec::with_capacity(parts);
1280 let mut new_frag_id = 10_000u32;
1281 for (part, gap) in gaps.iter().take(parts).enumerate() {
1282 let rows = base + u32::from((part as u32) < remainder);
1283 new_frags.push((new_frag_id, rows));
1284 new_frag_id += gap;
1285 }
1286
1287 let mut direct: HashMap<u64, Option<u64>> = HashMap::new();
1289 let mut read_order_iter = read_order.iter();
1290 for (new_frag_id, rows) in &new_frags {
1291 for offset in 0..*rows {
1292 let old = read_order_iter.next().unwrap();
1293 direct.insert(*old, Some(addr(*new_frag_id, offset)));
1294 }
1295 }
1296 prop_assert!(read_order_iter.next().is_none());
1297 for old in &covered {
1298 direct.entry(*old).or_insert(None);
1299 }
1300
1301 let compact = RowAddrRemap::compact([GroupInput {
1302 rewritten_old_row_addrs: rewritten,
1303 old_frag_ids: old_frag_ids.clone(),
1304 new_frags,
1305 }])
1306 .unwrap();
1307 let direct = RowAddrRemap::direct(direct);
1308
1309 for old in &covered {
1310 prop_assert_eq!(
1311 compact.get(*old),
1312 direct.get(*old),
1313 "address {:#x} in fragments {:?}",
1314 old,
1315 old_frag_ids
1316 );
1317 }
1318 for frag_id in [0xFFFF_FFF0u32, 0x7EEE_EEEE] {
1320 prop_assert_eq!(compact.get(addr(frag_id, 0)), None);
1321 }
1322 prop_assert_eq!(compact.affected_fragments(), direct.affected_fragments());
1323 }
1324 }
1325}