use std::collections::HashMap;
use std::collections::VecDeque;
use super::rtree_types::{PageId, Node};
pub struct CachedPage {
pub node: Node,
pub dirty: bool,
}
pub struct PageCache {
pub pages: HashMap<PageId, CachedPage>,
lru_order: VecDeque<PageId>,
max_pages: usize,
}
#[allow(dead_code)]
impl PageCache {
pub fn new(max_pages: usize) -> Self {
Self {
pages: HashMap::new(),
lru_order: VecDeque::new(),
max_pages,
}
}
pub fn get(&mut self, page_id: PageId) -> Option<&Node> {
if self.pages.contains_key(&page_id) {
self.lru_order.retain(|&id| id != page_id);
self.lru_order.push_back(page_id);
Some(&self.pages.get(&page_id).unwrap().node)
} else {
None
}
}
pub fn get_mut(&mut self, page_id: PageId) -> Option<&mut Node> {
if self.pages.contains_key(&page_id) {
self.lru_order.retain(|&id| id != page_id);
self.lru_order.push_back(page_id);
let cached = self.pages.get_mut(&page_id)?;
cached.dirty = true;
Some(&mut cached.node)
} else {
None
}
}
pub fn insert(&mut self, page_id: PageId, node: Node, dirty: bool) {
if self.pages.contains_key(&page_id) {
self.lru_order.retain(|&id| id != page_id);
}
self.lru_order.push_back(page_id);
self.pages.insert(page_id, CachedPage { node, dirty });
}
pub fn needs_eviction(&self) -> bool {
self.pages.len() >= self.max_pages
}
pub fn evict_oldest(&mut self) -> Option<(PageId, Node, bool)> {
while let Some(page_id) = self.lru_order.pop_front() {
if let Some(cached) = self.pages.remove(&page_id) {
return Some((page_id, cached.node, cached.dirty));
}
}
None
}
pub fn get_dirty_pages(&self) -> Vec<PageId> {
self.pages
.iter()
.filter(|(_, cached)| cached.dirty)
.map(|(id, _)| *id)
.collect()
}
pub fn mark_clean(&mut self, page_id: PageId) {
if let Some(cached) = self.pages.get_mut(&page_id) {
cached.dirty = false;
}
}
pub fn remove(&mut self, page_id: PageId) -> Option<(Node, bool)> {
self.lru_order.retain(|&id| id != page_id);
self.pages.remove(&page_id).map(|c| (c.node, c.dirty))
}
pub fn clear(&mut self) -> Vec<(PageId, Node, bool)> {
let result: Vec<_> = self
.pages
.drain()
.map(|(id, cached)| (id, cached.node, cached.dirty))
.collect();
self.lru_order.clear();
result
}
pub fn len(&self) -> usize {
self.pages.len()
}
pub fn is_empty(&self) -> bool {
self.pages.is_empty()
}
pub fn contains(&self, page_id: PageId) -> bool {
self.pages.contains_key(&page_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_page_cache_new() {
let cache = PageCache::new(10);
assert_eq!(cache.len(), 0);
assert!(!cache.needs_eviction());
}
#[test]
fn test_page_cache_insert_and_get() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node.clone(), false);
assert_eq!(cache.len(), 1);
assert!(cache.contains(1));
let retrieved = cache.get(1);
assert!(retrieved.is_some());
}
#[test]
fn test_page_cache_get_mut() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node, false);
let mut_ref = cache.get_mut(1);
assert!(mut_ref.is_some());
assert!(cache.pages.get(&1).unwrap().dirty);
}
#[test]
fn test_page_cache_lru_eviction() {
let mut cache = PageCache::new(3);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node.clone(), false);
cache.insert(2, node.clone(), false);
cache.insert(3, node.clone(), false);
assert!(cache.needs_eviction());
assert_eq!(cache.len(), 3);
let _ = cache.get(1);
let evicted = cache.evict_oldest();
assert!(evicted.is_some());
assert_eq!(evicted.unwrap().0, 2);
cache.insert(4, node.clone(), false);
assert!(!cache.contains(2));
assert!(cache.contains(1));
assert!(cache.contains(3));
assert!(cache.contains(4));
}
#[test]
fn test_page_cache_mark_clean() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node, true);
assert!(cache.pages.get(&1).unwrap().dirty);
cache.mark_clean(1);
assert!(!cache.pages.get(&1).unwrap().dirty);
}
#[test]
fn test_page_cache_get_dirty_pages() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node.clone(), true);
cache.insert(2, node.clone(), false);
cache.insert(3, node.clone(), true);
let dirty = cache.get_dirty_pages();
assert_eq!(dirty.len(), 2);
assert!(dirty.contains(&1));
assert!(dirty.contains(&3));
}
#[test]
fn test_page_cache_remove() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node, true);
assert_eq!(cache.len(), 1);
let removed = cache.remove(1);
assert!(removed.is_some());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_page_cache_clear() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node.clone(), true);
cache.insert(2, node.clone(), false);
cache.insert(3, node, true);
let cleared = cache.clear();
assert_eq!(cleared.len(), 3);
assert_eq!(cache.len(), 0);
assert_eq!(cleared.iter().filter(|(_, _, d)| *d).count(), 2);
}
#[test]
fn test_page_cache_evict_oldest() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node.clone(), false);
cache.insert(2, node.clone(), true);
cache.insert(3, node, false);
let evicted = cache.evict_oldest();
assert!(evicted.is_some());
assert_eq!(evicted.unwrap().0, 1);
assert_eq!(cache.len(), 2);
}
#[test]
fn test_page_cache_get_none() {
let mut cache = PageCache::new(10);
let result = cache.get(999);
assert!(result.is_none());
}
#[test]
fn test_page_cache_get_mut_none() {
let mut cache = PageCache::new(10);
let result = cache.get_mut(999);
assert!(result.is_none());
}
#[test]
fn test_page_cache_lru_order_get() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node.clone(), false);
cache.insert(2, node.clone(), false);
cache.insert(3, node, false);
let _ = cache.get(1);
let evicted1 = cache.evict_oldest();
assert_eq!(evicted1.unwrap().0, 2);
let evicted2 = cache.evict_oldest();
assert_eq!(evicted2.unwrap().0, 3);
let evicted3 = cache.evict_oldest();
assert_eq!(evicted3.unwrap().0, 1);
}
#[test]
fn test_page_cache_contains() {
let mut cache = PageCache::new(10);
let node = Node::Leaf {
entries: vec![],
};
cache.insert(1, node, false);
assert!(cache.contains(1));
assert!(!cache.contains(2));
}
}