1use crate::buffer_manager::BufferManager;
33use hashbrown::HashMap;
34use std::hash::{Hash, Hasher};
35use std::marker::PhantomData;
36use std::path::PathBuf;
37
38const SLOTS_PER_PAGE: u32 = 64;
40
41const PAGE_HEADER_SIZE: usize = 12;
43
44const FLAG_OCCUPIED: u8 = 0x01;
46const FLAG_DELETED: u8 = 0x02;
47
48#[derive(Debug, Clone)]
55pub struct HashIndex<K: Hash + Eq + Clone> {
56 entries: HashMap<K, u64>,
57}
58
59impl<K: Hash + Eq + Clone> HashIndex<K> {
60 pub fn new() -> Self {
61 Self {
62 entries: HashMap::new(),
63 }
64 }
65
66 pub fn insert(&mut self, key: K, row_offset: u64) {
67 self.entries.insert(key, row_offset);
68 }
69
70 pub fn lookup(&self, key: &K) -> Option<u64> {
71 self.entries.get(key).copied()
72 }
73
74 pub fn delete(&mut self, key: &K) {
75 self.entries.remove(key);
76 }
77
78 pub fn len(&self) -> usize {
79 self.entries.len()
80 }
81
82 pub fn is_empty(&self) -> bool {
83 self.entries.is_empty()
84 }
85
86 pub fn clear(&mut self) {
88 self.entries.clear();
89 }
90
91 pub fn iter(&self) -> impl Iterator<Item = (&K, &u64)> {
93 self.entries.iter()
94 }
95}
96
97impl<K: Hash + Eq + Clone> Default for HashIndex<K> {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103pub trait IndexKey: Clone + std::fmt::Debug {
110 fn key_size() -> usize;
112
113 fn serialize_into(&self, buf: &mut [u8]);
115
116 fn deserialize_from(buf: &[u8]) -> Self;
118}
119
120impl IndexKey for String {
122 fn key_size() -> usize {
123 8 }
125
126 fn serialize_into(&self, buf: &mut [u8]) {
127 let h = {
128 let mut hasher = std::collections::hash_map::DefaultHasher::new();
129 self.hash(&mut hasher);
130 hasher.finish()
131 };
132 buf.copy_from_slice(&h.to_le_bytes());
133 }
134
135 fn deserialize_from(buf: &[u8]) -> Self {
136 let h = u64::from_le_bytes(buf[..8].try_into().unwrap());
140 format!("__hash_{h}") }
142}
143
144impl IndexKey for u64 {
145 fn key_size() -> usize {
146 8
147 }
148
149 fn serialize_into(&self, buf: &mut [u8]) {
150 buf.copy_from_slice(&self.to_le_bytes());
151 }
152
153 fn deserialize_from(buf: &[u8]) -> Self {
154 u64::from_le_bytes(buf[..8].try_into().unwrap())
155 }
156}
157
158impl IndexKey for i64 {
159 fn key_size() -> usize {
160 8
161 }
162
163 fn serialize_into(&self, buf: &mut [u8]) {
164 buf.copy_from_slice(&self.to_le_bytes());
165 }
166
167 fn deserialize_from(buf: &[u8]) -> Self {
168 i64::from_le_bytes(buf[..8].try_into().unwrap())
169 }
170}
171
172impl IndexKey for u32 {
173 fn key_size() -> usize {
174 4
175 }
176
177 fn serialize_into(&self, buf: &mut [u8]) {
178 buf.copy_from_slice(&self.to_le_bytes());
179 }
180
181 fn deserialize_from(buf: &[u8]) -> Self {
182 u32::from_le_bytes(buf[..8].try_into().unwrap())
183 }
184}
185
186impl IndexKey for i32 {
187 fn key_size() -> usize {
188 4
189 }
190
191 fn serialize_into(&self, buf: &mut [u8]) {
192 buf.copy_from_slice(&self.to_le_bytes());
193 }
194
195 fn deserialize_from(buf: &[u8]) -> Self {
196 i32::from_le_bytes(buf[..8].try_into().unwrap())
197 }
198}
199
200#[derive(Debug)]
210pub struct OnDiskHashIndex<K: IndexKey> {
211 file_name: String,
213 key_size: usize,
215 slot_size: usize,
217 slots_per_page: u32,
219 cache: HashMap<K, u64>,
221 num_pages: u32,
223 _phantom: PhantomData<K>,
224}
225
226impl<K: IndexKey + Hash + Eq> OnDiskHashIndex<K> {
227 pub fn new(file_name: &str) -> Self {
232 let key_size = K::key_size();
233 let slot_size = key_size + 8 + 1; Self {
235 file_name: file_name.to_string(),
236 key_size,
237 slot_size,
238 slots_per_page: SLOTS_PER_PAGE,
239 cache: HashMap::new(),
240 num_pages: 0,
241 _phantom: PhantomData,
242 }
243 }
244
245 pub fn rebuild_from_disk(&mut self, bm: &mut BufferManager) -> std::io::Result<()> {
249 self.cache.clear();
250
251 let file_path = PathBuf::from(&self.file_name);
253 let db_path = file_path.parent().unwrap_or(&file_path);
254 let full_path = db_path.join(format!("{}.idx", self.file_name));
255 if !bm.is_file_registered(&self.file_name) {
256 bm.register_file(&self.file_name, full_path);
257 }
258
259 for page_num in 0..self.num_pages as u64 {
260 let frame = bm.pin(&self.file_name, page_num)?;
261 let data = &frame.data;
262
263 let num_slots = u32::from_le_bytes(data[0..4].try_into().unwrap());
264 let num_entries = u32::from_le_bytes(data[4..8].try_into().unwrap());
265 let _collision_next = u32::from_le_bytes(data[8..12].try_into().unwrap());
266
267 if num_entries == 0 {
268 bm.unpin(&self.file_name, page_num);
269 continue;
270 }
271
272 for slot_idx in 0..num_slots as usize {
273 let offset = PAGE_HEADER_SIZE + slot_idx * self.slot_size;
274 if offset + self.slot_size > data.len() {
275 break;
276 }
277 let flags = data[offset + self.key_size + 8];
278 if flags & FLAG_OCCUPIED == 0 || flags & FLAG_DELETED != 0 {
279 continue;
280 }
281
282 let mut key_buf = vec![0u8; self.key_size];
284 key_buf.copy_from_slice(&data[offset..offset + self.key_size]);
285
286 let value = u64::from_le_bytes(
288 data[offset + self.key_size..offset + self.key_size + 8]
289 .try_into()
290 .unwrap(),
291 );
292
293 if self.key_size <= 8 {
298 let key = K::deserialize_from(&key_buf);
299 self.cache.insert(key, value);
300 }
301 }
302
303 bm.unpin(&self.file_name, page_num);
304 }
305
306 Ok(())
307 }
308
309 pub fn len(&self) -> usize {
311 self.cache.len()
312 }
313
314 pub fn is_empty(&self) -> bool {
315 self.cache.is_empty()
316 }
317
318 fn hash_to_page(&self, key: &K) -> u32 {
322 let mut hasher = std::collections::hash_map::DefaultHasher::new();
323 key.hash(&mut hasher);
324 let hash = hasher.finish();
325 if self.num_pages == 0 {
326 0
327 } else {
328 (hash % self.num_pages as u64) as u32
329 }
330 }
331
332 fn serialize_key(key: &K, buf: &mut [u8]) {
334 key.serialize_into(buf);
335 }
336
337 fn slot_offset(&self, slot_idx: u32) -> usize {
339 PAGE_HEADER_SIZE + (slot_idx as usize) * self.slot_size
340 }
341
342 pub fn lookup_cached(&self, key: &K) -> Option<u64> {
348 self.cache.get(key).copied()
349 }
350
351 pub fn lookup(&self, key: &K, bm: &mut BufferManager) -> std::io::Result<Option<u64>> {
356 if let Some(offset) = self.cache.get(key) {
358 return Ok(Some(*offset));
359 }
360
361 if self.num_pages == 0 {
363 return Ok(None);
364 }
365
366 let page_num = self.hash_to_page(key) as u64;
367 let frame = bm.pin(&self.file_name, page_num)?;
368 let data = &frame.data;
369
370 let _num_slots = u32::from_le_bytes(data[0..4].try_into().unwrap());
371 let mut num_entries = u32::from_le_bytes(data[4..8].try_into().unwrap());
372 let mut collision_next = u32::from_le_bytes(data[8..12].try_into().unwrap());
373
374 let mut result = None;
375 let mut current_page = page_num;
376
377 loop {
378 if num_entries == 0 {
379 break;
380 }
381
382 let mut key_buf = vec![0u8; self.key_size];
383 let page_frame = bm.pin(&self.file_name, current_page)?;
384 let page_data = &page_frame.data;
385
386 let nslots = u32::from_le_bytes(page_data[0..4].try_into().unwrap());
387 for slot_idx in 0..nslots as usize {
388 let offset = PAGE_HEADER_SIZE + slot_idx * self.slot_size;
389 if offset + self.slot_size > page_data.len() {
390 break;
391 }
392 let flags = page_data[offset + self.key_size + 8];
393 if flags & FLAG_OCCUPIED == 0 || flags & FLAG_DELETED != 0 {
394 continue;
395 }
396
397 key_buf.copy_from_slice(&page_data[offset..offset + self.key_size]);
398 let candidate = K::deserialize_from(&key_buf);
399
400 if &candidate == key {
401 let value = u64::from_le_bytes(
402 page_data[offset + self.key_size..offset + self.key_size + 8]
403 .try_into()
404 .unwrap(),
405 );
406 result = Some(value);
407 bm.unpin(&self.file_name, current_page);
408 break;
409 }
410 }
411
412 bm.unpin(&self.file_name, current_page);
413
414 if result.is_some() {
415 break;
416 }
417
418 if collision_next == 0 {
419 break;
420 }
421 current_page = collision_next as u64;
422 let next_frame = bm.pin(&self.file_name, current_page)?;
423 let next_data = &next_frame.data;
424 num_entries = u32::from_le_bytes(next_data[4..8].try_into().unwrap());
425 collision_next = u32::from_le_bytes(next_data[8..12].try_into().unwrap());
426 bm.unpin(&self.file_name, current_page);
427 }
428
429 bm.unpin(&self.file_name, page_num);
434 Ok(result)
435 }
436
437 pub fn insert(&mut self, key: K, value: u64) {
441 self.cache.insert(key, value);
442 }
443
444 pub fn delete(&mut self, key: &K) {
446 self.cache.remove(key);
447 }
448
449 pub fn flush(&mut self, bm: &mut BufferManager) -> std::io::Result<()> {
454 if self.cache.is_empty() {
455 if self.num_pages > 0 {
456 let frame = bm.pin_mut(&self.file_name, 0)?;
458 let data = &mut frame.data;
459 data[0..4].copy_from_slice(&0u32.to_le_bytes()); data[4..8].copy_from_slice(&0u32.to_le_bytes()); data[8..12].copy_from_slice(&0u32.to_le_bytes()); frame.mark_dirty();
463 bm.unpin(&self.file_name, 0);
464 }
465 return bm.flush_all();
466 }
467
468 let num_entries = self.cache.len() as u32;
470 let pages_needed = num_entries.div_ceil(self.slots_per_page);
471 let pages_needed = pages_needed.max(1);
472
473 if pages_needed > self.num_pages {
475 self.num_pages = pages_needed;
476 }
477
478 for page_num in 0..self.num_pages as u64 {
480 let frame = bm.pin_mut(&self.file_name, page_num)?;
481 let data = &mut frame.data;
482
483 data.fill(0);
485
486 data[0..4].copy_from_slice(&self.slots_per_page.to_le_bytes());
488 data[4..8].copy_from_slice(&0u32.to_le_bytes()); data[8..12].copy_from_slice(&0u32.to_le_bytes()); frame.mark_dirty();
492 bm.unpin(&self.file_name, page_num);
493 }
494
495 if self.num_pages == 0 {
497 return Ok(());
498 }
499
500 let mut page_counts = vec![0u32; self.num_pages as usize];
502
503 for (key, _value) in self.cache.iter() {
505 let page = self.hash_to_page(key) as usize;
506 page_counts[page] = page_counts[page].saturating_add(1);
507 }
508
509 let mut page_cursors = vec![0u32; self.num_pages as usize];
511
512 for (key, value) in self.cache.iter() {
513 let page = self.hash_to_page(key) as u64;
514 let cursor = &mut page_cursors[page as usize];
515
516 let frame = bm.pin_mut(&self.file_name, page)?;
517 let data = &mut frame.data;
518
519 let slot_offset = self.slot_offset(*cursor);
520 if slot_offset + self.slot_size <= data.len() {
521 let mut key_buf = vec![0u8; self.key_size];
523 Self::serialize_key(key, &mut key_buf);
524 data[slot_offset..slot_offset + self.key_size].copy_from_slice(&key_buf);
525
526 data[slot_offset + self.key_size..slot_offset + self.key_size + 8]
528 .copy_from_slice(&value.to_le_bytes());
529
530 data[slot_offset + self.key_size + 8] = FLAG_OCCUPIED;
532
533 let current = u32::from_le_bytes(data[4..8].try_into().unwrap());
535 data[4..8].copy_from_slice(&(current + 1).to_le_bytes());
536 }
537
538 frame.mark_dirty();
539 bm.unpin(&self.file_name, page);
540
541 *cursor += 1;
542 }
543
544 bm.flush_all()
546 }
547
548 pub fn iter(&self) -> impl Iterator<Item = (&K, &u64)> {
550 self.cache.iter()
551 }
552}
553
554impl<K: IndexKey + Hash + Eq> Default for OnDiskHashIndex<K> {
555 fn default() -> Self {
556 Self::new("default")
557 }
558}
559
560#[cfg(test)]
565mod tests {
566 use super::*;
567 use crate::buffer_manager::BufferManagerConfig;
568 use crate::page::DEFAULT_PAGE_SIZE;
569 use akar_common::memory::MemoryManager;
570 use std::sync::Arc;
571
572 fn create_test_bm() -> (BufferManager, tempfile::TempDir) {
573 let dir = tempfile::tempdir().unwrap();
574 let mm = Arc::new(MemoryManager::new(1024 * 1024));
575 let config = BufferManagerConfig {
576 max_memory: 256 * 1024,
577 page_size: DEFAULT_PAGE_SIZE,
578 ..Default::default()
579 };
580 let mut bm = BufferManager::new(dir.path().to_path_buf(), mm, config);
581 let idx_path = dir.path().join("test_index.idx");
582 std::fs::write(&idx_path, vec![0u8; 8192 * 10]).unwrap();
583 bm.register_file("test_index", idx_path);
584 (bm, dir)
585 }
586
587 #[test]
588 fn test_hash_index_in_memory() {
589 let mut idx: HashIndex<String> = HashIndex::new();
590 idx.insert("Alice".to_string(), 0);
591 idx.insert("Bob".to_string(), 1);
592 assert_eq!(idx.lookup(&"Alice".to_string()), Some(0));
593 assert_eq!(idx.lookup(&"Bob".to_string()), Some(1));
594 assert_eq!(idx.lookup(&"Charlie".to_string()), None);
595 assert_eq!(idx.len(), 2);
596 }
597
598 #[test]
599 fn test_hash_index_delete() {
600 let mut idx: HashIndex<String> = HashIndex::new();
601 idx.insert("X".to_string(), 42);
602 assert_eq!(idx.lookup(&"X".to_string()), Some(42));
603 idx.delete(&"X".to_string());
604 assert_eq!(idx.lookup(&"X".to_string()), None);
605 assert!(idx.is_empty());
606 }
607
608 #[test]
609 fn test_on_disk_basic_insert_and_lookup() {
610 let (mut bm, _dir) = create_test_bm();
611 let mut idx: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
612
613 idx.insert(100u64, 0);
615 idx.insert(200u64, 1);
616 idx.insert(300u64, 2);
617
618 idx.flush(&mut bm).unwrap();
620
621 assert_eq!(idx.lookup_cached(&100), Some(0));
623 assert_eq!(idx.lookup_cached(&200), Some(1));
624 assert_eq!(idx.lookup_cached(&300), Some(2));
625 assert_eq!(idx.lookup_cached(&999), None);
626
627 assert_eq!(idx.lookup(&100, &mut bm).unwrap(), Some(0));
629 assert_eq!(idx.lookup(&200, &mut bm).unwrap(), Some(1));
630 assert_eq!(idx.lookup(&999, &mut bm).unwrap(), None);
631 }
632
633 #[test]
634 fn test_on_disk_multiple_pages() {
635 let (mut bm, _dir) = create_test_bm();
636 let mut idx: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
637
638 for i in 0..200u64 {
640 idx.insert(i * 1000, i);
641 }
642
643 idx.flush(&mut bm).unwrap();
644
645 assert!(idx.num_pages > 1, "Should have multiple pages for 200 entries");
647
648 for i in 0..200u64 {
650 assert_eq!(idx.lookup_cached(&(i * 1000)), Some(i));
651 }
652 }
653
654 #[test]
655 fn test_on_disk_delete() {
656 let (mut bm, _dir) = create_test_bm();
657 let mut idx: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
658
659 idx.insert(42u64, 0);
660 idx.insert(99u64, 1);
661 idx.flush(&mut bm).unwrap();
662
663 assert_eq!(idx.lookup_cached(&42), Some(0));
664
665 idx.delete(&42);
667 assert_eq!(idx.lookup_cached(&42), None);
668
669 idx.flush(&mut bm).unwrap();
671 assert_eq!(idx.lookup(&42, &mut bm).unwrap(), None);
672 }
673
674 #[test]
675 fn test_on_disk_rebuild_from_disk() {
676 let (mut bm, dir) = create_test_bm();
677 let mut idx: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
678
679 for i in 0..50u64 {
681 idx.insert(i, i);
682 }
683 idx.flush(&mut bm).unwrap();
684 assert_eq!(idx.len(), 50);
685 let pages_used = idx.num_pages;
686 assert!(pages_used > 0, "Should have at least 1 page for 50 entries");
687
688 let file_path = dir.path().join("test_index.idx");
690 let file_data = std::fs::read(&file_path).unwrap();
691 let header_num_slots = u32::from_le_bytes(file_data[0..4].try_into().unwrap());
692 let header_num_entries = u32::from_le_bytes(file_data[4..8].try_into().unwrap());
693 assert_eq!(header_num_slots, 64, "Should have 64 slots per page");
694 assert_eq!(header_num_entries, 50, "Should have 50 entries on disk");
695
696 let mut bm2 = BufferManager::new(
698 dir.path().to_path_buf(),
699 Arc::new(MemoryManager::new(1024 * 1024)),
700 BufferManagerConfig::default(),
701 );
702 bm2.register_file("test_index", file_path);
703
704 let mut idx2: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
705 idx2.num_pages = pages_used;
706 idx2.rebuild_from_disk(&mut bm2).unwrap();
707
708 assert_eq!(idx2.len(), 50);
710 for i in 0..50u64 {
711 assert_eq!(idx2.lookup_cached(&i), Some(i));
712 }
713 }
714
715 #[test]
716 fn test_on_disk_empty_index() {
717 let (mut bm, _dir) = create_test_bm();
718 let mut idx: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
719
720 idx.flush(&mut bm).unwrap();
722 assert_eq!(idx.len(), 0);
723 assert!(idx.is_empty());
724 }
725
726 #[test]
727 fn test_on_disk_large_entries() {
728 let (mut bm, _dir) = create_test_bm();
729 let mut idx: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
730
731 for i in 0..500u64 {
733 idx.insert(i, i + 1000);
734 }
735 idx.flush(&mut bm).unwrap();
736
737 for i in 0..500u64 {
739 assert_eq!(idx.lookup_cached(&i), Some(i + 1000));
740 }
741 }
742
743 #[test]
744 fn test_on_disk_collision_handling() {
745 let (mut bm, _dir) = create_test_bm();
746 let mut idx: OnDiskHashIndex<u64> = OnDiskHashIndex::new("test_index");
748 idx.num_pages = 2; for i in 0..100u64 {
751 idx.insert(i, i * 10);
752 }
753 idx.flush(&mut bm).unwrap();
754
755 for i in 0..100u64 {
757 assert_eq!(idx.lookup_cached(&i), Some(i * 10));
758 }
759 }
760}