Skip to main content

ferrum_kv/blocks/
table.rs

1//! Block table implementation for logical to physical mapping
2
3use ferrum_interfaces::BlockTable;
4use smallvec::SmallVec;
5
6/// Default implementation of block table
7#[derive(Debug, Clone, Default)]
8pub struct DefaultBlockTable {
9    /// Physical block usage bitmap
10    pub physical: SmallVec<[u32; 8]>,
11    /// Logical to physical block mapping
12    pub logical_to_physical: SmallVec<[u32; 8]>,
13    /// Sequence length
14    pub seq_len: usize,
15}
16
17impl DefaultBlockTable {
18    /// Create new block table
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Create with capacity
24    pub fn with_capacity(capacity: usize) -> Self {
25        Self {
26            physical: SmallVec::with_capacity(capacity),
27            logical_to_physical: SmallVec::with_capacity(capacity),
28            seq_len: 0,
29        }
30    }
31
32    /// Add logical to physical mapping
33    pub fn map_block(&mut self, logical_id: u32, physical_id: u32) {
34        // Ensure capacity
35        while self.logical_to_physical.len() <= logical_id as usize {
36            self.logical_to_physical.push(0);
37        }
38        while self.physical.len() <= physical_id as usize {
39            self.physical.push(0);
40        }
41
42        self.logical_to_physical[logical_id as usize] = physical_id;
43        self.physical[physical_id as usize] = 1; // Mark as used
44    }
45
46    /// Remove mapping for logical block
47    pub fn unmap_block(&mut self, logical_id: u32) -> Option<u32> {
48        if (logical_id as usize) < self.logical_to_physical.len() {
49            let physical_id = self.logical_to_physical[logical_id as usize];
50            if physical_id > 0 && (physical_id as usize) < self.physical.len() {
51                self.physical[physical_id as usize] = 0; // Mark as free
52                self.logical_to_physical[logical_id as usize] = 0;
53                Some(physical_id)
54            } else {
55                None
56            }
57        } else {
58            None
59        }
60    }
61
62    /// Get physical block ID for logical block
63    pub fn get_physical(&self, logical_id: u32) -> Option<u32> {
64        if (logical_id as usize) < self.logical_to_physical.len() {
65            let physical_id = self.logical_to_physical[logical_id as usize];
66            if physical_id > 0 {
67                Some(physical_id)
68            } else {
69                None
70            }
71        } else {
72            None
73        }
74    }
75
76    /// Check if physical block is used
77    pub fn is_physical_used(&self, physical_id: u32) -> bool {
78        if (physical_id as usize) < self.physical.len() {
79            self.physical[physical_id as usize] != 0
80        } else {
81            false
82        }
83    }
84
85    /// Get number of logical blocks
86    pub fn num_logical_blocks(&self) -> usize {
87        self.logical_to_physical.len()
88    }
89
90    /// Get number of physical blocks allocated
91    pub fn num_physical_blocks(&self) -> usize {
92        self.physical.len()
93    }
94
95    /// Get number of used physical blocks
96    pub fn num_used_blocks(&self) -> usize {
97        self.physical.iter().filter(|&&used| used != 0).count()
98    }
99
100    /// Clear all mappings
101    pub fn clear(&mut self) {
102        self.physical.clear();
103        self.logical_to_physical.clear();
104        self.seq_len = 0;
105    }
106
107    /// Shrink block table to remove unused entries
108    pub fn shrink(&mut self) {
109        // Remove trailing zeros from logical_to_physical
110        while let Some(&last) = self.logical_to_physical.last() {
111            if last == 0 {
112                self.logical_to_physical.pop();
113            } else {
114                break;
115            }
116        }
117
118        // Remove trailing zeros from physical
119        while let Some(&last) = self.physical.last() {
120            if last == 0 {
121                self.physical.pop();
122            } else {
123                break;
124            }
125        }
126    }
127
128    /// Get all used physical block IDs
129    pub fn used_physical_blocks(&self) -> Vec<u32> {
130        self.physical
131            .iter()
132            .enumerate()
133            .filter_map(|(id, &used)| if used != 0 { Some(id as u32) } else { None })
134            .collect()
135    }
136
137    /// Create a copy with extended capacity
138    pub fn extend(&self, additional_logical: usize, additional_physical: usize) -> Self {
139        let mut new_table = self.clone();
140        new_table.logical_to_physical.reserve(additional_logical);
141        new_table.physical.reserve(additional_physical);
142        new_table
143    }
144}
145
146impl From<DefaultBlockTable> for BlockTable {
147    fn from(table: DefaultBlockTable) -> Self {
148        BlockTable {
149            physical_blocks: table.physical,
150            logical_to_physical: table.logical_to_physical,
151            sequence_length: table.seq_len,
152            block_size: 16, // Default block size
153        }
154    }
155}
156
157impl From<BlockTable> for DefaultBlockTable {
158    fn from(table: BlockTable) -> Self {
159        Self {
160            physical: table.physical_blocks.iter().copied().collect(),
161            logical_to_physical: table.logical_to_physical.iter().copied().collect(),
162            seq_len: table.sequence_length,
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_block_table_creation() {
173        let table = DefaultBlockTable::new();
174        assert_eq!(table.num_logical_blocks(), 0);
175        assert_eq!(table.num_physical_blocks(), 0);
176        assert_eq!(table.seq_len, 0);
177    }
178
179    #[test]
180    fn test_block_mapping() {
181        let mut table = DefaultBlockTable::new();
182
183        // Map logical block 0 to physical block 5
184        table.map_block(0, 5);
185
186        assert_eq!(table.get_physical(0), Some(5));
187        assert!(table.is_physical_used(5));
188        assert_eq!(table.num_used_blocks(), 1);
189    }
190
191    #[test]
192    fn test_block_unmapping() {
193        let mut table = DefaultBlockTable::new();
194
195        table.map_block(0, 5);
196        assert_eq!(table.get_physical(0), Some(5));
197
198        let unmapped = table.unmap_block(0);
199        assert_eq!(unmapped, Some(5));
200        assert_eq!(table.get_physical(0), None);
201        assert!(!table.is_physical_used(5));
202        assert_eq!(table.num_used_blocks(), 0);
203    }
204
205    #[test]
206    fn test_multiple_mappings() {
207        let mut table = DefaultBlockTable::new();
208
209        table.map_block(0, 10);
210        table.map_block(1, 20);
211        table.map_block(2, 30);
212
213        assert_eq!(table.get_physical(0), Some(10));
214        assert_eq!(table.get_physical(1), Some(20));
215        assert_eq!(table.get_physical(2), Some(30));
216        assert_eq!(table.num_used_blocks(), 3);
217
218        let used_blocks = table.used_physical_blocks();
219        assert!(used_blocks.contains(&10));
220        assert!(used_blocks.contains(&20));
221        assert!(used_blocks.contains(&30));
222    }
223
224    #[test]
225    fn test_table_shrink() {
226        let mut table = DefaultBlockTable::new();
227
228        table.map_block(0, 5);
229        table.map_block(1, 6);
230        table.unmap_block(1); // This should leave trailing zeros
231
232        assert!(table.logical_to_physical.len() >= 2);
233        table.shrink();
234
235        // After shrinking, trailing zeros should be removed
236        assert_eq!(table.logical_to_physical.len(), 1);
237        assert_eq!(table.get_physical(0), Some(5));
238    }
239
240    #[test]
241    fn test_table_clear() {
242        let mut table = DefaultBlockTable::new();
243
244        table.map_block(0, 5);
245        table.map_block(1, 6);
246        table.seq_len = 100;
247
248        assert!(table.num_logical_blocks() > 0);
249        assert!(table.num_physical_blocks() > 0);
250
251        table.clear();
252
253        assert_eq!(table.num_logical_blocks(), 0);
254        assert_eq!(table.num_physical_blocks(), 0);
255        assert_eq!(table.seq_len, 0);
256    }
257
258    #[test]
259    fn test_with_capacity() {
260        let table = DefaultBlockTable::with_capacity(10);
261        assert!(table.logical_to_physical.capacity() >= 10);
262        assert!(table.physical.capacity() >= 10);
263    }
264
265    #[test]
266    fn test_extend() {
267        let table = DefaultBlockTable::new();
268        let extended = table.extend(5, 10);
269
270        assert!(extended.logical_to_physical.capacity() >= 5);
271        assert!(extended.physical.capacity() >= 10);
272    }
273
274    #[test]
275    fn test_conversion() {
276        let mut default_table = DefaultBlockTable::new();
277        default_table.map_block(0, 5);
278        default_table.seq_len = 50;
279
280        // Convert to BlockTable
281        let block_table: BlockTable = default_table.clone().into();
282        assert_eq!(block_table.sequence_length, 50);
283        assert_eq!(block_table.logical_to_physical[0], 5);
284
285        // Convert back to DefaultBlockTable
286        let back_to_default: DefaultBlockTable = block_table.into();
287        assert_eq!(back_to_default.seq_len, 50);
288        assert_eq!(back_to_default.get_physical(0), Some(5));
289    }
290}