code_moniker_workspace/snapshot/
records.rs1use std::ops::Index;
10use std::sync::Arc;
11
12#[derive(Clone, Debug)]
13pub struct RecordTable<T> {
14 shards: Vec<Arc<[T]>>,
15 offsets: Vec<usize>,
16}
17
18impl<T> RecordTable<T> {
19 pub fn from_shards(shards: Vec<Arc<[T]>>) -> Self {
20 let mut table = Self {
21 shards,
22 offsets: Vec::new(),
23 };
24 table.rebuild_offsets();
25 table
26 }
27
28 pub fn from_records(records: Vec<T>) -> Self {
29 Self::from_shards(vec![Arc::from(records)])
30 }
31
32 pub fn len(&self) -> usize {
33 self.offsets.last().copied().unwrap_or(0)
34 }
35
36 pub fn is_empty(&self) -> bool {
37 self.len() == 0
38 }
39
40 pub fn get(&self, index: usize) -> Option<&T> {
41 if index >= self.len() {
42 return None;
43 }
44 let slot = self.offsets.partition_point(|offset| *offset <= index) - 1;
45 self.shards[slot].get(index - self.offsets[slot])
46 }
47
48 pub fn iter(&self) -> impl Iterator<Item = &T> + '_ {
49 self.shards.iter().flat_map(|shard| shard.iter())
50 }
51
52 pub fn file_records(&self, slot: usize) -> &[T] {
53 self.shards.get(slot).map(Arc::as_ref).unwrap_or(&[])
54 }
55
56 pub(crate) fn replace(&mut self, slot: usize, records: Arc<[T]>) {
57 if let Some(shard) = self.shards.get_mut(slot) {
58 *shard = records;
59 } else if slot == self.shards.len() {
60 self.shards.push(records);
61 }
62 self.rebuild_offsets();
63 }
64
65 fn rebuild_offsets(&mut self) {
66 self.offsets.clear();
67 self.offsets.reserve(self.shards.len() + 1);
68 let mut total = 0usize;
69 self.offsets.push(0);
70 for shard in &self.shards {
71 total += shard.len();
72 self.offsets.push(total);
73 }
74 }
75}
76
77impl<T> Index<usize> for RecordTable<T> {
78 type Output = T;
79
80 fn index(&self, index: usize) -> &T {
81 self.get(index)
82 .unwrap_or_else(|| panic!("record index {index} out of bounds"))
83 }
84}
85
86impl<T: PartialEq> PartialEq for RecordTable<T> {
87 fn eq(&self, other: &Self) -> bool {
88 self.len() == other.len() && self.iter().eq(other.iter())
89 }
90}
91
92impl<T: Eq> Eq for RecordTable<T> {}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 fn table() -> RecordTable<u32> {
99 RecordTable::from_shards(vec![
100 Arc::from(vec![1u32, 2]),
101 Arc::from(Vec::<u32>::new()),
102 Arc::from(vec![3u32, 4, 5]),
103 ])
104 }
105
106 #[test]
107 fn positions_span_shards_with_empty_slots() {
108 let table = table();
109 assert_eq!(table.len(), 5);
110 assert_eq!(table.get(0), Some(&1));
111 assert_eq!(table.get(1), Some(&2));
112 assert_eq!(table.get(2), Some(&3));
113 assert_eq!(table.get(4), Some(&5));
114 assert_eq!(table.get(5), None);
115 assert_eq!(
116 table.iter().copied().collect::<Vec<_>>(),
117 vec![1, 2, 3, 4, 5]
118 );
119 assert_eq!(table.file_records(1), &[] as &[u32]);
120 assert_eq!(table.file_records(9), &[] as &[u32]);
121 }
122
123 #[test]
124 fn replace_swaps_one_shard_and_reindexes() {
125 let mut table = table();
126 table.replace(0, Arc::from(vec![9u32]));
127 assert_eq!(table.iter().copied().collect::<Vec<_>>(), vec![9, 3, 4, 5]);
128 assert_eq!(table[1], 3);
129 }
130
131 #[test]
132 fn equality_ignores_shard_boundaries() {
133 let flat = RecordTable::from_records(vec![1u32, 2, 3, 4, 5]);
134 assert_eq!(table(), flat);
135 }
136}