use crate::buffer::FrameId;
use crate::error::{QuillSQLError, QuillSQLResult};
use crate::utils::cache::Replacer;
use std::collections::HashMap;
#[derive(Debug)]
pub struct ClockLRUReplacer {
current_size: usize,
capacity: usize,
clock_hand: usize,
frames: Vec<Option<FrameEntry>>,
frame_map: HashMap<FrameId, usize>,
}
#[derive(Debug, Clone)]
struct FrameEntry {
frame_id: FrameId,
referenced: bool,
is_evictable: bool,
}
impl FrameEntry {
fn new(frame_id: FrameId) -> Self {
Self {
frame_id,
referenced: false, is_evictable: false,
}
}
}
impl Replacer for ClockLRUReplacer {
fn new(capacity: usize) -> Self {
Self {
current_size: 0,
capacity,
clock_hand: 0,
frames: vec![None; capacity],
frame_map: HashMap::with_capacity(capacity),
}
}
fn record_access(&mut self, frame_id: FrameId) -> QuillSQLResult<()> {
if let Some(&index) = self.frame_map.get(&frame_id) {
if let Some(entry) = &mut self.frames[index] {
entry.referenced = true;
}
} else {
if self.frame_map.len() >= self.capacity {
return Err(QuillSQLError::Internal(
"frame size exceeds capacity".to_string(),
));
}
let mut slot_index = None;
for (i, slot) in self.frames.iter().enumerate() {
if slot.is_none() {
slot_index = Some(i);
break;
}
}
let index = slot_index.unwrap_or_else(|| {
panic!("No available slot found, this should not happen");
});
let entry = FrameEntry::new(frame_id); self.frames[index] = Some(entry);
self.frame_map.insert(frame_id, index);
}
Ok(())
}
fn evict(&mut self) -> Option<FrameId> {
if self.current_size == 0 {
return None;
}
let start_clock_hand = self.clock_hand;
let mut first_evictable = None;
let mut first_evictable_pos = None;
loop {
if let Some(entry) = &mut self.frames[self.clock_hand] {
if entry.is_evictable {
if first_evictable.is_none() {
first_evictable = Some(entry.frame_id);
first_evictable_pos = Some(self.clock_hand);
}
if !entry.referenced {
let frame_id = entry.frame_id;
let current_pos = self.clock_hand;
self.clock_hand = (self.clock_hand + 1) % self.capacity;
self.frames[current_pos] = None;
self.frame_map.remove(&frame_id);
self.current_size -= 1;
return Some(frame_id);
} else {
entry.referenced = false;
}
}
}
self.clock_hand = (self.clock_hand + 1) % self.capacity;
if self.clock_hand == start_clock_hand {
break;
}
}
if let Some(frame_id) = first_evictable {
if let Some(pos) = first_evictable_pos {
self.frames[pos] = None;
self.frame_map.remove(&frame_id);
self.current_size -= 1;
self.clock_hand = (pos + 1) % self.capacity;
return Some(frame_id);
}
}
None
}
fn set_evictable(&mut self, frame_id: FrameId, set_evictable: bool) -> QuillSQLResult<()> {
if let Some(&index) = self.frame_map.get(&frame_id) {
if let Some(entry) = &mut self.frames[index] {
let old_evictable = entry.is_evictable;
entry.is_evictable = set_evictable;
if !old_evictable && set_evictable {
self.current_size += 1;
} else if old_evictable && !set_evictable {
self.current_size -= 1;
}
return Ok(());
}
}
Err(QuillSQLError::Internal(format!(
"frame {} not found",
frame_id
)))
}
fn remove(&mut self, frame_id: FrameId) {
if let Some(index) = self.frame_map.remove(&frame_id) {
if let Some(entry) = &self.frames[index] {
if entry.is_evictable {
self.current_size -= 1;
}
}
self.frames[index] = None;
}
}
fn size(&self) -> usize {
self.current_size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clock_lru_basic() {
let mut replacer = ClockLRUReplacer::new(3);
replacer.record_access(1).unwrap();
replacer.record_access(2).unwrap();
replacer.record_access(3).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
replacer.set_evictable(3, true).unwrap();
assert_eq!(replacer.size(), 3);
let evicted = replacer.evict();
assert_eq!(evicted, Some(1));
assert_eq!(replacer.size(), 2);
}
#[test]
fn test_clock_lru_reference_bit() {
let mut replacer = ClockLRUReplacer::new(3);
replacer.record_access(1).unwrap();
replacer.record_access(2).unwrap();
replacer.record_access(3).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
replacer.set_evictable(3, true).unwrap();
replacer.record_access(1).unwrap();
let evicted = replacer.evict();
assert_eq!(evicted, Some(2));
let evicted = replacer.evict();
assert_eq!(evicted, Some(3));
assert_eq!(replacer.size(), 1);
}
#[test]
fn test_clock_lru_remove() {
let mut replacer = ClockLRUReplacer::new(3);
replacer.record_access(1).unwrap();
replacer.record_access(2).unwrap();
replacer.record_access(3).unwrap();
replacer.set_evictable(1, true).unwrap();
replacer.set_evictable(2, true).unwrap();
replacer.set_evictable(3, true).unwrap();
assert_eq!(replacer.size(), 3);
replacer.remove(2);
assert_eq!(replacer.size(), 2);
let evicted = replacer.evict();
assert_eq!(evicted, Some(1));
assert_eq!(replacer.size(), 1);
}
#[test]
fn test_clock_lru_complex() {
let mut replacer = ClockLRUReplacer::new(5);
for i in 1..=5 {
replacer.record_access(i).unwrap();
replacer.set_evictable(i, true).unwrap();
}
assert_eq!(replacer.size(), 5);
replacer.record_access(2).unwrap();
replacer.record_access(4).unwrap();
let evicted = replacer.evict();
assert_eq!(evicted, Some(1));
let evicted = replacer.evict();
assert_eq!(evicted, Some(3));
let evicted = replacer.evict();
assert_eq!(evicted, Some(5));
assert_eq!(replacer.size(), 2);
replacer.record_access(2).unwrap();
let evicted = replacer.evict();
assert_eq!(evicted, Some(4));
assert_eq!(replacer.size(), 1);
}
}