Skip to main content

akar_storage/
shadow_file.rs

1//! Shadow file — copy-on-write versioning for pages during transactions.
2//!
3//! During a transaction, shadow pages track modifications to database pages.
4//! On commit, `apply()` finalises the changes (writes shadow data to the
5//! BufferManager). On rollback, `discard()` drops all shadow pages.
6
7use crate::buffer_manager::BufferManager;
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10
11/// A shadow file entry tracking an original → modified page mapping.
12#[derive(Debug, Clone)]
13pub struct ShadowEntry {
14    pub original_page_id: u64,
15    pub shadow_data: Vec<u8>,
16    pub is_dirty: bool,
17}
18
19/// Manages shadow pages for copy-on-write during a transaction.
20#[derive(Debug, Default)]
21pub struct ShadowFile {
22    entries: HashMap<u64, ShadowEntry>,
23    /// Name of the file in the BufferManager these shadow pages belong to.
24    file_name: Option<String>,
25}
26
27impl ShadowFile {
28    pub fn new() -> Self {
29        Self {
30            entries: HashMap::new(),
31            file_name: None,
32        }
33    }
34
35    /// Set the BufferManager file name that this shadow file tracks.
36    pub fn set_file_name(&mut self, name: &str) {
37        self.file_name = Some(name.to_string());
38    }
39
40    pub fn create_shadow(&mut self, page_id: u64, data: Vec<u8>) {
41        self.entries.insert(
42            page_id,
43            ShadowEntry {
44                original_page_id: page_id,
45                shadow_data: data,
46                is_dirty: true,
47            },
48        );
49    }
50
51    pub fn get_shadow(&self, page_id: u64) -> Option<&ShadowEntry> {
52        self.entries.get(&page_id)
53    }
54
55    pub fn has_shadow(&self, page_id: u64) -> bool {
56        self.entries.contains_key(&page_id)
57    }
58
59    /// Number of shadow entries.
60    pub fn len(&self) -> usize {
61        self.entries.len()
62    }
63
64    pub fn is_empty(&self) -> bool {
65        self.entries.is_empty()
66    }
67
68    /// Clear all shadow entries (used during rollback to discard changes).
69    pub fn clear(&mut self) {
70        self.entries.clear();
71    }
72
73    /// Apply all dirty shadow pages to the BufferManager.
74    ///
75    /// For each shadow entry, pins the page, writes the shadow data, marks it
76    /// dirty, and unpins. This makes the transaction's writes visible.
77    ///
78    /// Call this on commit **after** the WAL has been flushed.
79    pub fn apply(&self, buffer_manager: &Arc<Mutex<BufferManager>>) -> std::io::Result<()> {
80        let file_name = match &self.file_name {
81            Some(n) => n.clone(),
82            None => return Ok(()), // No file to write to
83        };
84
85        let mut bm = buffer_manager
86            .lock()
87            .map_err(|e| std::io::Error::other(format!("Lock poisoned: {e}")))?;
88        for entry in self.entries.values().filter(|e| e.is_dirty) {
89            let frame = bm.pin_mut(&file_name, entry.original_page_id)?;
90            // Copy shadow data into the frame
91            let copy_len = entry.shadow_data.len().min(frame.data.len());
92            frame.data[..copy_len].copy_from_slice(&entry.shadow_data[..copy_len]);
93            frame.mark_dirty();
94            bm.unpin(&file_name, entry.original_page_id);
95        }
96        Ok(())
97    }
98
99    /// Discard all shadow entries (rollback).
100    ///
101    /// Simply clears the entries — the original pages in the BufferManager
102    /// were never modified, so no undo is needed at the page level.
103    pub fn discard(&mut self) {
104        self.entries.clear();
105    }
106
107    pub fn dirty_pages(&self) -> impl Iterator<Item = &ShadowEntry> {
108        self.entries.values().filter(|e| e.is_dirty)
109    }
110}