umadb_core/
mvcc.rs

1// use std::cell::RefCell;
2use crate::common::Position;
3use crate::common::{PageID, Tsn};
4use crate::events_tree_nodes::EventLeafNode;
5use crate::free_lists_tree_nodes::{
6    FreeListInternalNode, FreeListLeafNode, FreeListLeafValue, FreeListTsnLeafNode,
7};
8use crate::header_node::HeaderNode;
9use crate::node::Node;
10use crate::page::{PAGE_HEADER_SIZE, Page, serialize_page_into};
11use crate::pager::Pager;
12use crate::tags_tree_nodes::TagsLeafNode;
13use umadb_dcb::{DCBError, DCBResult};
14// use rayon::prelude::*;
15// use std::os::unix::fs::FileExt; // For write_at on Unix
16use dashmap::DashMap;
17use std::collections::HashMap;
18use std::collections::VecDeque;
19use std::path::Path;
20use std::sync::atomic::{AtomicUsize, Ordering};
21use std::sync::{Arc, Mutex};
22use std::thread::sleep;
23use std::time::Duration;
24// use crate::db::DEFAULT_PAGE_SIZE;
25
26const GET_LATEST_HEADER_RETRIES: usize = 5;
27const GET_LATEST_HEADER_DELAY: Duration = Duration::from_millis(10);
28const HEADER_PAGE_ID_0: PageID = PageID(0);
29const HEADER_PAGE_ID_1: PageID = PageID(1);
30
31// thread_local! {
32//     static PAGE_BUF: RefCell<Vec<u8>> = RefCell::new(vec![0u8; DEFAULT_PAGE_SIZE]);
33// }
34
35// Main MVCC structure
36pub struct Mvcc {
37    pub pager: Pager,
38    pub reader_tsns: Arc<DashMap<usize, Tsn>>,
39    pub writer_lock: Mutex<()>,
40    pub page_size: usize,
41    pub max_node_size: usize,
42    // Owned header node instances for pages 0 and 1
43    pub headers: Mutex<Vec<Page>>,
44    // Reusable buffer for header serialization
45    pub header_page_buf: Mutex<Vec<u8>>,
46    // Reusable buffer for general page serialization
47    pub page_buf: Mutex<Vec<u8>>,
48    reader_id_counter: AtomicUsize,
49    pub verbose: bool,
50}
51
52impl Mvcc {
53    pub fn new(path: &Path, page_size: usize, verbose: bool) -> DCBResult<Self> {
54        let pager = Pager::new(path, page_size)?;
55
56        let mvcc = Self {
57            pager,
58            reader_tsns: Arc::new(DashMap::new()),
59            writer_lock: Mutex::new(()),
60            page_size,
61            max_node_size: page_size - PAGE_HEADER_SIZE,
62            headers: Mutex::new(vec![
63                Page {
64                    page_id: PageID(0),
65                    node: Node::Header(HeaderNode::default()),
66                },
67                Page {
68                    page_id: PageID(1),
69                    node: Node::Header(HeaderNode::default()),
70                },
71            ]),
72            header_page_buf: Mutex::new(vec![0u8; page_size]),
73            page_buf: Mutex::new(vec![0u8; page_size]),
74            reader_id_counter: AtomicUsize::new(0),
75            verbose,
76        };
77
78        if mvcc.pager.is_file_new {
79            // Initialize new database
80            let initial_tsn = Tsn(0);
81            let initial_free_lists_tree_root_id = PageID(2);
82            let initial_events_tree_root_id = PageID(3);
83            let initial_tags_tree_root_id = PageID(4);
84            let initial_next_page_id = PageID(5);
85            let initial_next_position = Position(1);
86            mvcc.update_header(
87                HEADER_PAGE_ID_0,
88                initial_tsn,
89                initial_free_lists_tree_root_id,
90                initial_events_tree_root_id,
91                initial_tags_tree_root_id,
92                initial_next_page_id,
93                initial_next_position,
94            )?;
95            mvcc.update_header(
96                HEADER_PAGE_ID_1,
97                initial_tsn,
98                initial_free_lists_tree_root_id,
99                initial_events_tree_root_id,
100                initial_tags_tree_root_id,
101                initial_next_page_id,
102                initial_next_position,
103            )?;
104
105            // Create and write an empty free lists tree root page.
106            let free_list_leaf = FreeListLeafNode {
107                keys: Vec::new(),
108                values: Vec::new(),
109            };
110            let free_list_page = Page::new(
111                initial_free_lists_tree_root_id,
112                Node::FreeListLeaf(free_list_leaf),
113            );
114
115            // Create and write an empty events tree root page.
116            let event_leaf = EventLeafNode {
117                keys: Vec::new(),
118                values: Vec::new(),
119            };
120            let position_page = Page::new(initial_events_tree_root_id, Node::EventLeaf(event_leaf));
121
122            // Create and write an empty tags tree root page.
123            let tags_leaf = TagsLeafNode {
124                keys: Vec::new(),
125                values: Vec::new(),
126            };
127            let tags_page = Page::new(initial_tags_tree_root_id, Node::TagsLeaf(tags_leaf));
128
129            // Write all three initial root pages using the shared write_pages helper
130            let _ = mvcc.write_pages([&free_list_page, &position_page, &tags_page].into_iter())?;
131
132            mvcc.flush()?;
133        }
134
135        Ok(mvcc)
136    }
137
138    pub fn get_latest_header(&self) -> DCBResult<(PageID, HeaderNode)> {
139        for attempt in 0..GET_LATEST_HEADER_RETRIES {
140            let h0 = self.read_header(HEADER_PAGE_ID_0);
141            let h1 = self.read_header(HEADER_PAGE_ID_1);
142
143            match (h0, h1) {
144                (Ok(header0), Ok(header1)) => {
145                    if header1.tsn > header0.tsn {
146                        return Ok((HEADER_PAGE_ID_1, header1));
147                    } else {
148                        return Ok((HEADER_PAGE_ID_0, header0));
149                    }
150                }
151                (Ok(header0), Err(_)) => {
152                    return Ok((HEADER_PAGE_ID_0, header0));
153                }
154                (Err(_), Ok(header1)) => {
155                    return Ok((HEADER_PAGE_ID_1, header1));
156                }
157                (Err(e0), Err(e1)) => {
158                    if attempt + 1 < GET_LATEST_HEADER_RETRIES {
159                        if self.verbose {
160                            println!(
161                                "Both headers invalid on attempt {}: {:?} | {:?}. Retrying...",
162                                attempt + 1,
163                                e0,
164                                e1
165                            );
166                        }
167                        sleep(GET_LATEST_HEADER_DELAY);
168                        continue;
169                    } else {
170                        return Err(DCBError::DatabaseCorrupted(format!(
171                            "Both header pages appear corrupted after {} attempts: ({:?}) and ({:?})",
172                            GET_LATEST_HEADER_RETRIES, e0, e1
173                        )));
174                    }
175                }
176            }
177        }
178        // Unreachable due to returns in match and final error, but keep to satisfy type checker
179        Err(DCBError::DatabaseCorrupted(
180            "Unable to read a valid header".to_string(),
181        ))
182    }
183
184    pub fn read_header(&self, page_id: PageID) -> DCBResult<HeaderNode> {
185        let page = self.read_page(page_id)?;
186        match page.node {
187            Node::Header(node) => Ok(node),
188            _ => Err(DCBError::DatabaseCorrupted(
189                "Invalid header node type".to_string(),
190            )),
191        }
192    }
193
194    fn update_header(
195        &self,
196        page_id: PageID,
197        tsn: Tsn,
198        free_lists_tree_root_id: PageID,
199        events_tree_root_id: PageID,
200        tags_tree_root_id: PageID,
201        next_page_id: PageID,
202        next_position: Position,
203    ) -> DCBResult<()> {
204        let mut headers = self.headers.lock().unwrap();
205        let headers_idx = { if page_id == HEADER_PAGE_ID_0 { 0 } else { 1 } };
206        let header = &mut headers[headers_idx];
207        match &mut header.node {
208            Node::Header(node) => {
209                // Update node values.
210                node.tsn = tsn;
211                node.free_lists_tree_root_id = free_lists_tree_root_id;
212                node.events_tree_root_id = events_tree_root_id;
213                node.tags_tree_root_id = tags_tree_root_id;
214                node.next_page_id = next_page_id;
215                node.next_position = next_position;
216
217                // Write node using pre-allocated buffer.
218                let mut buf = self.page_buf.lock().unwrap();
219                serialize_page_into(&mut buf, &header.node)?;
220                self.pager.write_page(page_id, &buf)?;
221                Ok(())
222            }
223            _ => panic!("Shouldn't get here: header should be a header"),
224        }
225    }
226
227    pub fn read_page(&self, page_id: PageID) -> DCBResult<Page> {
228        let mapped = self.pager.read_page_mmap_slice(page_id)?;
229        if self.verbose {
230            println!("Read {page_id:?} from file, deserializing...");
231        }
232        Page::deserialize(page_id, mapped.as_slice())
233    }
234
235    pub fn flush(&self) -> DCBResult<()> {
236        self.pager.flush()?;
237        Ok(())
238    }
239
240    pub fn reader(&self) -> DCBResult<Reader> {
241        let (header_page_id, header_node) = self.get_latest_header()?;
242
243        // Generate a unique ID for this reader using the counter (lock-free)
244        let reader_id = self.reader_id_counter.fetch_add(1, Ordering::Relaxed) + 1;
245
246        // Register the reader TSN (lock-free concurrent insert)
247        self.reader_tsns.insert(reader_id, header_node.tsn);
248
249        // Create the reader with the unique ID
250        let reader = Reader {
251            header_page_id,
252            tsn: header_node.tsn,
253            events_tree_root_id: header_node.events_tree_root_id,
254            tags_tree_root_id: header_node.tags_tree_root_id,
255            next_position: header_node.next_position,
256            reader_id,
257            reader_tsns: Arc::clone(&self.reader_tsns),
258        };
259
260        Ok(reader)
261    }
262
263    pub fn writer(&self) -> DCBResult<Writer> {
264        if self.verbose {
265            println!();
266            println!("Constructing writer...");
267        }
268
269        // Get the latest header
270        let (header_page_id, header_node) = self.get_latest_header()?;
271
272        // Create the writer
273        let mut writer = Writer::new(
274            header_page_id,
275            Tsn(header_node.tsn.0 + 1),
276            header_node.next_page_id,
277            header_node.free_lists_tree_root_id,
278            header_node.events_tree_root_id,
279            header_node.tags_tree_root_id,
280            header_node.next_position,
281            self.verbose,
282        );
283
284        if self.verbose {
285            println!("Constructed writer with {:?}", writer.tsn);
286        }
287
288        // Find the reusable page IDs.
289        writer.find_reusable_page_ids(self)?;
290
291        Ok(writer)
292    }
293
294    /// Write one or more pages to disk using the shared preallocated page buffer.
295    /// Returns the number of pages written.
296    pub fn write_pages<'a, I>(&self, pages: I) -> DCBResult<usize>
297    where
298        I: IntoIterator<Item = &'a Page>,
299    {
300        let mut buf = self.page_buf.lock().unwrap();
301        let mut count = 0usize;
302        for page in pages {
303            page.serialize_into(&mut buf)?;
304            self.pager.write_page(page.page_id, &buf)?;
305            if self.verbose {
306                println!("Wrote {:?} to file", page.page_id);
307            }
308            count += 1;
309        }
310        Ok(count)
311    }
312
313    // pub fn write_pages_parallel<'a, I>(&self, pages: I) -> DCBResult<usize>
314    // where
315    //     I: IntoIterator<Item = &'a Page> + Send,
316    //     I::IntoIter: Send,
317    // {
318    //     let pages: Vec<&Page> = pages.into_iter().collect();
319    //     if pages.is_empty() {
320    //         return Ok(0);
321    //     }
322    //
323    //     let file = self.pager.writer.clone();
324    //     let page_size = self.page_size;
325    //     let verbose = self.verbose;
326    //
327    //     let count: DCBResult<usize> = pages
328    //         .par_iter()
329    //         .map(|page| -> DCBResult<usize> {
330    //             PAGE_BUF.with(|cell| -> DCBResult<usize> {
331    //                 let mut buf = cell.borrow_mut();
332    //                 if buf.len() != page_size {
333    //                     *buf = vec![0u8; page_size]; // lazy init
334    //                 }
335    //
336    //                 page.serialize_into(&mut buf)?;
337    //                 file.write_at(&buf, page.page_id.0 * page_size as u64)?;
338    //
339    //                 if verbose {
340    //                     println!("Wrote {:?} to file", page.page_id);
341    //                 }
342    //                 Ok(1)
343    //             })
344    //         })
345    //         .try_reduce(|| 0, |a, b| Ok(a + b));
346    //
347    //     count
348    // }
349
350    pub fn commit(&self, writer: &mut Writer) -> DCBResult<()> {
351        // Process reused and freed page IDs
352        if self.verbose {
353            println!();
354            println!("Commiting writer with {:?}", writer.tsn);
355        }
356
357        while !writer.reused_page_ids.is_empty() || !writer.freed_page_ids.is_empty() {
358            // Remove reused page IDs from free lists.
359            while let Some((reused_page_id, tsn)) = writer.reused_page_ids.pop_front() {
360                // Remove the reused page ID from the freed list tree
361                writer.remove_free_page_id(self, tsn, reused_page_id)?;
362            }
363
364            // Process freed page IDs
365            while let Some(freed_page_id) = writer.freed_page_ids.pop_front() {
366                // Remove dirty pages that were also freed
367                writer.dirty.remove(&freed_page_id);
368
369                // Insert the page ID in the freed list tree
370                writer.insert_freed_page_id(self, writer.tsn, freed_page_id)?;
371            }
372        }
373
374        // Write all dirty pages (except for the header page) to the file
375        if !writer.dirty.is_empty() {
376            let count = {
377                // let num_dirty = writer.dirty.len();
378                // println!("Number dirty pages: {num_dirty}");
379                // if num_dirty >= 0 {
380                //     self.write_pages_parallel(writer.dirty.values())?
381                // } else {
382                //     self.write_pages(writer.dirty.values())?
383                // }
384                self.write_pages(writer.dirty.values())?
385            };
386            if self.verbose {
387                println!("Wrote {} dirty page(s) to file", count);
388            }
389        }
390
391        // Flush the file to disk
392        self.flush()?;
393
394        // Mutate the owned header instance and serialize into the preallocated buffer
395        self.update_header(
396            if writer.header_page_id == HEADER_PAGE_ID_0 {
397                HEADER_PAGE_ID_1
398            } else {
399                HEADER_PAGE_ID_0
400            },
401            writer.tsn,
402            writer.free_lists_tree_root_id,
403            writer.events_tree_root_id,
404            writer.tags_tree_root_id,
405            writer.next_page_id,
406            writer.next_position,
407        )?;
408
409        // Flush the file to disk
410        self.flush()?;
411
412        if self.verbose {
413            println!("Committed writer with {:?}", writer.tsn);
414        }
415
416        Ok(())
417    }
418}
419
420// Writer transaction
421pub struct Writer {
422    pub header_page_id: PageID,
423    pub tsn: Tsn,
424    pub next_page_id: PageID,
425    pub free_lists_tree_root_id: PageID,
426    pub events_tree_root_id: PageID,
427    pub tags_tree_root_id: PageID,
428    pub next_position: Position,
429    pub reusable_page_ids: VecDeque<(PageID, Tsn)>,
430    pub freed_page_ids: VecDeque<PageID>,
431    pub deserialized: HashMap<PageID, Page>,
432    pub dirty: HashMap<PageID, Page>,
433    pub reused_page_ids: VecDeque<(PageID, Tsn)>,
434    pub verbose: bool,
435}
436
437impl Writer {
438    pub fn new(
439        header_page_id: PageID,
440        tsn: Tsn,
441        next_page_id: PageID,
442        free_lists_tree_root_id: PageID,
443        events_tree_root_id: PageID,
444        tags_tree_root_id: PageID,
445        next_position: Position,
446        verbose: bool,
447    ) -> Self {
448        Self {
449            header_page_id,
450            tsn,
451            next_page_id,
452            free_lists_tree_root_id,
453            events_tree_root_id,
454            tags_tree_root_id,
455            next_position,
456            reusable_page_ids: VecDeque::new(),
457            freed_page_ids: VecDeque::new(),
458            deserialized: HashMap::new(),
459            dirty: HashMap::new(),
460            reused_page_ids: VecDeque::new(),
461            verbose,
462        }
463    }
464
465    /// Returns the current issue position and increments the next position.
466    ///
467    /// Returns:
468    ///
469    /// The current issue position.
470    pub fn issue_position(&mut self) -> Position {
471        let pos = self.next_position;
472        self.next_position = Position(self.next_position.0 + 1);
473        pos
474    }
475
476    /// Retrieves a reference to the page with the given ID.
477    ///
478    /// First checks the dirty pages map, then the deserialized pages map.
479    /// If the page is not found in either map, it is deserialized from the MVCC.
480    ///
481    /// # Arguments
482    ///
483    /// * `mvcc`: A reference to the MVCC.
484    /// * `page_id`: The ID of the page to retrieve.
485    ///
486    /// # Returns
487    ///
488    /// A `DCBResult` containing a reference to the page if found, or an error if the page could not be found.
489    pub fn get_page_ref(&mut self, mvcc: &Mvcc, page_id: PageID) -> DCBResult<&Page> {
490        // Check the dirty pages first
491        if self.dirty.contains_key(&page_id) {
492            return Ok(self.dirty.get(&page_id).unwrap());
493        }
494
495        // Then check deserialized pages
496        if self.deserialized.contains_key(&page_id) {
497            return Ok(self.deserialized.get(&page_id).unwrap());
498        }
499
500        // Need to deserialize the page
501        let deserialized_page = mvcc.read_page(page_id)?;
502        self.insert_deserialized(deserialized_page);
503
504        // Return the deserialized page
505        Ok(self.deserialized.get(&page_id).unwrap())
506    }
507
508    pub fn get_mut_dirty(&mut self, page_id: PageID) -> DCBResult<&mut Page> {
509        if let Some(page) = self.dirty.get_mut(&page_id) {
510            Ok(page)
511        } else {
512            Err(DCBError::DirtyPageNotFound(page_id.0))
513        }
514    }
515
516    pub fn insert_deserialized(&mut self, page: Page) {
517        self.deserialized.insert(page.page_id, page);
518    }
519
520    pub fn insert_dirty(&mut self, page: Page) -> DCBResult<()> {
521        if self.freed_page_ids.contains(&page.page_id) {
522            return Err(DCBError::PageAlreadyFreed(page.page_id.0));
523        }
524        if self.dirty.contains_key(&page.page_id) {
525            return Err(DCBError::PageAlreadyDirty(page.page_id.0));
526        }
527        self.dirty.insert(page.page_id, page);
528        Ok(())
529    }
530
531    pub fn alloc_page_id(&mut self) -> PageID {
532        if let Some((free_page_id, tsn)) = self.reusable_page_ids.pop_front() {
533            self.reused_page_ids.push_back((free_page_id, tsn));
534            return free_page_id;
535        }
536
537        let next_page_id = self.next_page_id;
538        self.next_page_id = PageID(next_page_id.0 + 1);
539        next_page_id
540    }
541
542    pub fn get_dirty_page_id(&mut self, page_id: PageID) -> DCBResult<PageID> {
543        let mut dirty_page_id = page_id;
544        if !self.freed_page_ids.iter().any(|&id| id == page_id) {
545            if !self.dirty.contains_key(&page_id) {
546                let old_page_id = page_id;
547                self.freed_page_ids.push_back(old_page_id);
548
549                let new_page_id = self.alloc_page_id();
550                let old_page = self.deserialized.get(&old_page_id).unwrap();
551                let new_page = Page {
552                    page_id: new_page_id,
553                    node: old_page.node.clone(),
554                };
555
556                self.dirty.insert(new_page_id, new_page);
557                if self.verbose {
558                    println!(
559                        "Copied {:?} to {:?}: {:?}",
560                        old_page_id, new_page_id, old_page.node
561                    );
562                }
563                dirty_page_id = new_page_id;
564            } else if self.verbose {
565                println!("{page_id:?} is already dirty");
566            }
567        } else {
568            return Err(DCBError::PageAlreadyFreed(page_id.0));
569        }
570        Ok(dirty_page_id)
571    }
572
573    pub fn append_freed_page_id(&mut self, page_id: PageID) {
574        let verbose = self.verbose;
575        if !self.freed_page_ids.iter().any(|&id| id == page_id) {
576            self.freed_page_ids.push_back(page_id);
577            if verbose {
578                println!("Appended {page_id:?} to freed_page_ids");
579            }
580            if self.dirty.contains_key(&page_id) {
581                self.dirty.remove(&page_id);
582                if verbose {
583                    println!("Page ID {page_id:?} was in dirty and was removed");
584                }
585            }
586            if verbose && self.dirty.contains_key(&page_id) {
587                println!("Page ID {page_id:?} is still in dirty!!!!!");
588            }
589        }
590    }
591
592    pub fn find_reusable_page_ids(&mut self, mvcc: &Mvcc) -> DCBResult<()> {
593        let verbose = self.verbose;
594        let mut reusable_page_ids: VecDeque<(PageID, Tsn)> = VecDeque::new();
595        // Get free page IDs
596        if verbose {
597            println!("Finding reusable page IDs for TSN {:?}...", self.tsn);
598        }
599
600        // Find the smallest reader TSN (lock-free iteration over concurrent map)
601        let smallest_reader_tsn = mvcc.reader_tsns.iter().map(|r| *r.value()).min();
602        if verbose {
603            println!("Smallest reader TSN: {smallest_reader_tsn:?}");
604        }
605
606        if verbose {
607            println!("Root is {:?}", self.free_lists_tree_root_id);
608        }
609        // Walk the tree to find leaf nodes
610        let mut stack = vec![(self.free_lists_tree_root_id, 0)];
611        let mut is_finished = false;
612
613        while let Some((page_id, idx)) = stack.pop() {
614            if is_finished {
615                break;
616            }
617            let node_owned = {
618                match self.get_page_ref(mvcc, page_id) {
619                    Ok(p) => p.node.clone(),
620                    Err(e) => {
621                        return Err(DCBError::DatabaseCorrupted(format!(
622                            "Free list page {:?} load error: {:?}",
623                            page_id, e
624                        )));
625                    }
626                }
627            };
628            match node_owned {
629                Node::FreeListInternal(node) => {
630                    if verbose {
631                        println!("{:?} is internal node", page_id);
632                    }
633                    if idx < node.child_ids.len() {
634                        let child_page_id = node.child_ids[idx];
635                        stack.push((page_id, idx + 1));
636                        stack.push((child_page_id, 0));
637                    }
638                }
639                Node::FreeListLeaf(node) => {
640                    if verbose {
641                        println!("{:?} is leaf node", page_id);
642                    }
643                    for i in 0..node.keys.len() {
644                        let tsn = node.keys[i];
645                        if let Some(smallest) = smallest_reader_tsn
646                            && tsn > smallest
647                        {
648                            is_finished = true;
649                            break;
650                        }
651
652                        let leaf_value = &node.values[i];
653                        if leaf_value.root_id == PageID(0) {
654                            for &page_id in &leaf_value.page_ids {
655                                reusable_page_ids.push_back((page_id, tsn));
656                            }
657                        } else {
658                            // Traverse into the TSN-subtree rooted at root_id using the same (node, idx)
659                            // iterative DFS pattern as the main FreeList traversal. This preserves
660                            // left-to-right order deterministically.
661                            let mut tsn_stack: Vec<(PageID, usize)> = vec![(leaf_value.root_id, 0)];
662                            if self.verbose {
663                                println!("TSN-subtree root_id: {:?}", leaf_value.root_id);
664                            }
665                            if self.verbose {
666                                println!(
667                                    "root_id in dirty? {}",
668                                    self.dirty.contains_key(&leaf_value.root_id)
669                                );
670                            }
671                            while let Some((sub_id, sidx)) = tsn_stack.pop() {
672                                let sub_node = {
673                                    match self.get_page_ref(mvcc, sub_id) {
674                                        Ok(p) => p.node.clone(),
675                                        Err(e) => {
676                                            return Err(DCBError::DatabaseCorrupted(format!(
677                                                "TSN subtree page {:?} load error: {:?}",
678                                                sub_id, e
679                                            )));
680                                        }
681                                    }
682                                };
683                                match sub_node {
684                                    Node::FreeListTsnInternal(tsn_internal) => {
685                                        if sidx < tsn_internal.child_ids.len() {
686                                            let child_id = tsn_internal.child_ids[sidx];
687                                            tsn_stack.push((sub_id, sidx + 1));
688                                            tsn_stack.push((child_id, 0));
689                                        }
690                                    }
691                                    Node::FreeListTsnLeaf(tsn_leaf) => {
692                                        for &pid in &tsn_leaf.page_ids {
693                                            reusable_page_ids.push_back((pid, tsn));
694                                        }
695                                    }
696                                    other => {
697                                        return Err(DCBError::DatabaseCorrupted(format!(
698                                            "Invalid node type in TSN subtree: {}",
699                                            other.type_name()
700                                        )));
701                                    }
702                                }
703                            }
704                        }
705                    }
706                }
707                _ => {
708                    return Err(DCBError::DatabaseCorrupted(
709                        "Invalid node type in free list tree".to_string(),
710                    ));
711                }
712            }
713        }
714
715        self.reusable_page_ids = reusable_page_ids;
716        if verbose {
717            println!("Found reusable page IDs: {:?}", self.reusable_page_ids);
718        }
719        Ok(())
720    }
721
722    // Free list tree methods
723    pub fn insert_freed_page_id(
724        &mut self,
725        mvcc: &Mvcc,
726        tsn: Tsn,
727        freed_page_id: PageID,
728    ) -> DCBResult<()> {
729        let verbose = self.verbose;
730        if verbose {
731            println!("Inserting {freed_page_id:?} for {tsn:?}");
732            println!("Root is {:?}", self.free_lists_tree_root_id);
733        }
734        // Get the root page ID.
735        let mut current_page_id = self.free_lists_tree_root_id;
736
737        // Traverse the tree to find a leaf node
738        let mut stack: Vec<PageID> = Vec::new();
739        let plan: FreePageIDInsertStrategy;
740        loop {
741            let current_page_ref = self.get_page_ref(mvcc, current_page_id)?;
742            if let Node::FreeListLeaf(leaf_node) = &current_page_ref.node {
743                let len_keys = leaf_node.keys.len();
744                if len_keys == 0 {
745                    if !leaf_node.would_fit_new_tsn_and_page_id(mvcc.max_node_size) {
746                        return Err(DCBError::InternalError("Page size too small".to_string()));
747                    }
748                    plan = FreePageIDInsertStrategy::PushTsnOntoFreeListLeaf;
749                } else {
750                    let last_idx = len_keys - 1;
751                    let last_key = leaf_node.keys[last_idx];
752                    if tsn == last_key {
753                        // Append to the existing last TSN
754                        if leaf_node.values[last_idx].root_id != PageID(0) {
755                            plan = FreePageIDInsertStrategy::PushPageIdOntoExistingTsnSubtree;
756                        } else if leaf_node.would_fit_new_page_id(mvcc.max_node_size) {
757                            plan = FreePageIDInsertStrategy::PushPageIdOntoFreeListLeaf(last_idx);
758                        } else if leaf_node.keys.len() == 1 {
759                            plan = FreePageIDInsertStrategy::MoveTsnToNewTsnSubtree;
760                        } else {
761                            plan = FreePageIDInsertStrategy::SplitFreeListLeaf;
762                        }
763                    } else if tsn > last_key {
764                        // New last TSN
765                        if leaf_node.would_fit_new_tsn_and_page_id(mvcc.max_node_size) {
766                            plan = FreePageIDInsertStrategy::PushTsnOntoFreeListLeaf;
767                        } else {
768                            plan = FreePageIDInsertStrategy::CreateAndPromoteFreeListLeaf;
769                        }
770                    } else {
771                        // We assume freed page IDs are always inserted for the last TSN
772                        return Err(DCBError::InternalError(
773                            "Insertion only supported for last TSN in leaf".to_string(),
774                        ));
775                    }
776                }
777                break;
778            }
779            if let Node::FreeListInternal(internal_node) = &current_page_ref.node {
780                if verbose {
781                    println!("{:?} is internal node", current_page_ref.page_id);
782                }
783                stack.push(current_page_id);
784                current_page_id = *internal_node
785                    .child_ids
786                    .last()
787                    .expect("FreeListInternal node should have a child");
788            } else {
789                return Err(DCBError::DatabaseCorrupted(
790                    "Expected FreeListInternal node".to_string(),
791                ));
792            }
793        }
794        if verbose {
795            println!("{current_page_id:?} is leaf node");
796        }
797        // Make the leaf page dirty
798        let dirty_leaf_page_id = { self.get_dirty_page_id(current_page_id)? };
799        let replacement_info: Option<(PageID, PageID)> = {
800            if dirty_leaf_page_id != current_page_id {
801                Some((current_page_id, dirty_leaf_page_id))
802            } else {
803                None
804            }
805        };
806        // Proactive insert logic with capacity checks and optional split
807        let mut split_info: Option<(Tsn, PageID)> = None;
808
809        // First handle TSN-subtree plans without holding a mutable borrow to the freelist leaf
810        match plan {
811            FreePageIDInsertStrategy::PushPageIdOntoExistingTsnSubtree => {
812                // Read leaf immutably to find last_idx and current root_id
813                let leaf_snapshot = { self.get_page_ref(mvcc, dirty_leaf_page_id)? };
814                let Node::FreeListLeaf(leaf_ro) = &leaf_snapshot.node else {
815                    return Err(DCBError::DatabaseCorrupted(
816                        "Expected FreeListLeaf node".to_string(),
817                    ));
818                };
819                let last_idx = leaf_ro.keys.len() - 1;
820                let tsn_root_id = leaf_ro.values[last_idx].root_id;
821                if tsn_root_id == PageID(0) {
822                    return Err(DCBError::DatabaseCorrupted(
823                        "Expected TSN-subtree root_id to be set".to_string(),
824                    ));
825                }
826                let new_root_id = self.tsn_subtree_insert(mvcc, tsn_root_id, freed_page_id)?;
827                if new_root_id != tsn_root_id {
828                    // Now mutate the freelist leaf to update root_id
829                    let dirty_leaf_page = self.get_mut_dirty(dirty_leaf_page_id)?;
830                    let Node::FreeListLeaf(dirty_leaf_node2) = &mut dirty_leaf_page.node else {
831                        return Err(DCBError::DatabaseCorrupted(
832                            "Expected FreeListLeaf node".to_string(),
833                        ));
834                    };
835                    dirty_leaf_node2.values[last_idx].root_id = new_root_id;
836                }
837            }
838            FreePageIDInsertStrategy::MoveTsnToNewTsnSubtree => {
839                // Read leaf immutably to capture inline page_ids
840                let leaf_snapshot = { self.get_page_ref(mvcc, dirty_leaf_page_id)? };
841                let Node::FreeListLeaf(leaf_ro) = &leaf_snapshot.node else {
842                    return Err(DCBError::DatabaseCorrupted(
843                        "Expected FreeListLeaf node".to_string(),
844                    ));
845                };
846                let last_idx = leaf_ro.keys.len() - 1;
847                let mut page_ids = leaf_ro.values[last_idx].page_ids.clone();
848                page_ids.push(freed_page_id);
849                // sort + dedup the inline ids plus the new id
850                page_ids.sort_by_key(|pid| pid.0);
851                page_ids.dedup();
852                // Build the initial TSN-subtree: start with a single leaf that fits, then insert the rest
853                // Create an empty leaf and add as many as fit by size
854                let mut initial_ids: Vec<PageID> = Vec::new();
855                let mut tmp_leaf = FreeListTsnLeafNode {
856                    page_ids: Vec::new(),
857                };
858                for pid in &page_ids {
859                    let mut candidate = tmp_leaf.clone();
860                    candidate.page_ids.push(*pid);
861                    let candidate_page = Page::new(PageID(0), Node::FreeListTsnLeaf(candidate));
862                    if candidate_page.calc_serialized_size() <= mvcc.page_size {
863                        tmp_leaf.page_ids.push(*pid);
864                        initial_ids.push(*pid);
865                    } else {
866                        break;
867                    }
868                }
869                if initial_ids.is_empty() {
870                    return Err(DCBError::InternalError(
871                        "Page size too small for TSN-subtree leaf with one PageID".to_string(),
872                    ));
873                }
874                let tsn_leaf_id = self.alloc_page_id();
875                let tsn_leaf_page = Page::new(tsn_leaf_id, Node::FreeListTsnLeaf(tmp_leaf));
876                self.insert_dirty(tsn_leaf_page)?;
877                // Root id starts as this leaf
878                let mut tsn_root_id = tsn_leaf_id;
879                // Insert remaining ids via general insert, root may change
880                for pid in page_ids.into_iter().filter(|p| !initial_ids.contains(p)) {
881                    tsn_root_id = self.tsn_subtree_insert(mvcc, tsn_root_id, pid)?;
882                }
883                // Now mutate the freelist leaf to clear inline and set root
884                let dirty_leaf_page = self.get_mut_dirty(dirty_leaf_page_id)?;
885                let Node::FreeListLeaf(dirty_leaf_node2) = &mut dirty_leaf_page.node else {
886                    return Err(DCBError::DatabaseCorrupted(
887                        "Expected FreeListLeaf node".to_string(),
888                    ));
889                };
890                dirty_leaf_node2.values[last_idx].page_ids.clear();
891                dirty_leaf_node2.values[last_idx].root_id = tsn_root_id;
892                if verbose {
893                    println!("Moved inline page IDs to TSN-subtree {:?}", tsn_root_id);
894                }
895            }
896            _ => { /* handled below with a mutable freelist leaf borrow */ }
897        }
898
899        // Now handle the remaining plans that only mutate the freelist leaf
900        let dirty_leaf_page = self.get_mut_dirty(dirty_leaf_page_id)?;
901        if let Node::FreeListLeaf(dirty_leaf_node) = &mut dirty_leaf_page.node {
902            match plan {
903                FreePageIDInsertStrategy::PushTsnOntoFreeListLeaf => {
904                    dirty_leaf_node.push_new_key_and_value(tsn, freed_page_id);
905                    if verbose {
906                        println!(
907                            "Inserted first pair ({tsn:?} -> {freed_page_id:?}) in {dirty_leaf_page_id:?}: {:?}",
908                            dirty_leaf_node
909                        );
910                    }
911                }
912                FreePageIDInsertStrategy::PushPageIdOntoFreeListLeaf(last_idx) => {
913                    dirty_leaf_node.push_new_page_id(last_idx, freed_page_id);
914                    if verbose {
915                        println!(
916                            "Appended {freed_page_id:?} for existing last {tsn:?} in {dirty_leaf_page_id:?}: {:?}",
917                            dirty_leaf_node
918                        );
919                    }
920                }
921                FreePageIDInsertStrategy::PushPageIdOntoExistingTsnSubtree => { /* handled earlier */
922                }
923                FreePageIDInsertStrategy::MoveTsnToNewTsnSubtree => { /* handled earlier */ }
924                FreePageIDInsertStrategy::SplitFreeListLeaf => {
925                    let (popped_key, mut popped_value) =
926                        dirty_leaf_node.pop_last_key_and_value()?;
927                    debug_assert_eq!(popped_key, tsn);
928                    if verbose {
929                        println!(
930                            "Split (last TSN) leaf {:?}: {:?}",
931                            dirty_leaf_page_id,
932                            dirty_leaf_node.clone()
933                        );
934                    }
935                    // Move the overflowing TSN to a new page and append there
936                    popped_value.page_ids.push(freed_page_id);
937                    let new_leaf_node = FreeListLeafNode {
938                        keys: vec![popped_key],
939                        values: vec![popped_value],
940                    };
941                    let new_leaf_page_id = self.alloc_page_id();
942                    let new_leaf_page =
943                        Page::new(new_leaf_page_id, Node::FreeListLeaf(new_leaf_node));
944                    // let serialized_size = new_leaf_page.calc_serialized_size();
945                    // if serialized_size > mvcc.page_size {
946                    //     return Err(DCBError::InternalError(
947                    //         "Shouldn't get here: page size is too small for a FreeListLeafNode with one TSN and one PageID".to_string(),
948                    //     ));
949                    // }
950                    if verbose {
951                        println!(
952                            "Created new leaf {:?} (moved last TSN): {:?}",
953                            new_leaf_page_id, new_leaf_page.node
954                        );
955                    }
956                    self.insert_dirty(new_leaf_page)?;
957                    split_info = Some((tsn, new_leaf_page_id));
958                }
959                FreePageIDInsertStrategy::CreateAndPromoteFreeListLeaf => {
960                    // Create a new leaf containing only this last TSN and promote it
961                    let new_leaf_node = FreeListLeafNode {
962                        keys: vec![tsn],
963                        values: vec![FreeListLeafValue {
964                            page_ids: vec![freed_page_id],
965                            root_id: PageID(0),
966                        }],
967                    };
968                    let new_leaf_page_id = self.alloc_page_id();
969                    let new_leaf_page =
970                        Page::new(new_leaf_page_id, Node::FreeListLeaf(new_leaf_node));
971                    // let serialized_size = new_leaf_page.calc_serialized_size();
972                    // if serialized_size > mvcc.page_size {
973                    //     return Err(DCBError::InternalError(
974                    //         "Shouldn't get here: page size is too small for a FreeListLeafNode with one TSN and one PageID".to_string(),
975                    //     ));
976                    // }
977                    if verbose {
978                        println!(
979                            "Created new leaf {:?} (new last TSN): {:?}",
980                            new_leaf_page_id, new_leaf_page.node
981                        );
982                    }
983                    self.insert_dirty(new_leaf_page)?;
984                    split_info = Some((tsn, new_leaf_page_id));
985                }
986            }
987        } else {
988            return Err(DCBError::DatabaseCorrupted(
989                "Expected FreeListLeaf node".to_string(),
990            ));
991        }
992        // Propagate splits and replacements up the stack
993        let mut current_replacement_info = replacement_info;
994        while let Some(parent_page_id) = stack.pop() {
995            // Make the internal page dirty
996            let dirty_page_id = { self.get_dirty_page_id(parent_page_id)? };
997            let parent_replacement_info: Option<(PageID, PageID)> = {
998                if dirty_page_id != parent_page_id {
999                    Some((parent_page_id, dirty_page_id))
1000                } else {
1001                    None
1002                }
1003            };
1004            // Get a mutable internal node....
1005            let dirty_internal_page = self.get_mut_dirty(dirty_page_id)?;
1006
1007            if let Node::FreeListInternal(dirty_internal_node) = &mut dirty_internal_page.node {
1008                if let Some((old_id, new_id)) = current_replacement_info {
1009                    dirty_internal_node.replace_last_child_id(old_id, new_id)?;
1010                    if verbose {
1011                        println!(
1012                            "Replaced {old_id:?} with {new_id:?} in {dirty_page_id:?}: {dirty_internal_node:?}"
1013                        );
1014                    }
1015                } else if verbose {
1016                    println!("Nothing to replace in {dirty_page_id:?}")
1017                }
1018            } else {
1019                return Err(DCBError::DatabaseCorrupted(
1020                    "Expected FreeListInternal node".to_string(),
1021                ));
1022            }
1023
1024            if let Some((promoted_key, promoted_page_id)) = split_info {
1025                if let Node::FreeListInternal(dirty_internal_node) = &mut dirty_internal_page.node {
1026                    // Add the promoted key and page ID
1027                    dirty_internal_node
1028                        .append_promoted_key_and_page_id(promoted_key, promoted_page_id)?;
1029
1030                    if verbose {
1031                        println!(
1032                            "Appended promoted key {promoted_key:?} and child {promoted_page_id:?} in {dirty_page_id:?}: {dirty_internal_node:?}"
1033                        );
1034                    }
1035                } else {
1036                    return Err(DCBError::DatabaseCorrupted(
1037                        "Expected FreeListInternal node".to_string(),
1038                    ));
1039                }
1040            }
1041
1042            // Check if the internal page needs splitting
1043
1044            if dirty_internal_page.calc_serialized_size() > mvcc.page_size {
1045                if let Node::FreeListInternal(dirty_internal_node) = &mut dirty_internal_page.node {
1046                    if verbose {
1047                        println!("Splitting internal {dirty_page_id:?}...");
1048                    }
1049                    // Split the internal node
1050                    // Ensure we have at least 3 keys and 4 child IDs before splitting
1051                    if dirty_internal_node.keys.len() < 3 || dirty_internal_node.child_ids.len() < 4
1052                    {
1053                        return Err(DCBError::DatabaseCorrupted(
1054                            "Cannot split internal node with too few keys/children".to_string(),
1055                        ));
1056                    }
1057
1058                    // Move the right-most key to a new node. Promote the next right-most key.
1059                    let (promoted_key, new_keys, new_child_ids) =
1060                        dirty_internal_node.split_off()?;
1061
1062                    // Ensure old node maintain the B-tree invariant: n keys should have n+1 child pointers
1063                    assert_eq!(
1064                        dirty_internal_node.keys.len() + 1,
1065                        dirty_internal_node.child_ids.len()
1066                    );
1067
1068                    let new_internal_node = FreeListInternalNode {
1069                        keys: new_keys,
1070                        child_ids: new_child_ids,
1071                    };
1072
1073                    // Ensure the new node also maintains the invariant
1074                    assert_eq!(
1075                        new_internal_node.keys.len() + 1,
1076                        new_internal_node.child_ids.len()
1077                    );
1078
1079                    // Create a new internal page.
1080                    let new_internal_page_id = self.alloc_page_id();
1081                    let new_internal_page = Page::new(
1082                        new_internal_page_id,
1083                        Node::FreeListInternal(new_internal_node),
1084                    );
1085                    if verbose {
1086                        println!(
1087                            "Created internal {:?}: {:?}",
1088                            new_internal_page_id, new_internal_page.node
1089                        );
1090                    }
1091                    self.insert_dirty(new_internal_page)?;
1092
1093                    split_info = Some((promoted_key, new_internal_page_id));
1094                } else {
1095                    return Err(DCBError::DatabaseCorrupted(
1096                        "Expected FreeListInternal node".to_string(),
1097                    ));
1098                }
1099            } else {
1100                split_info = None;
1101            }
1102            current_replacement_info = parent_replacement_info;
1103        }
1104
1105        if let Some((old_id, new_id)) = current_replacement_info {
1106            if self.free_lists_tree_root_id == old_id {
1107                self.free_lists_tree_root_id = new_id;
1108                if verbose {
1109                    println!("Replaced root {old_id:?} with {new_id:?}");
1110                }
1111            } else {
1112                return Err(DCBError::RootIDMismatch(old_id.0, new_id.0));
1113            }
1114        }
1115
1116        if let Some((promoted_key, promoted_page_id)) = split_info {
1117            // Create a new root
1118            let new_internal_node = FreeListInternalNode {
1119                keys: vec![promoted_key],
1120                child_ids: vec![self.free_lists_tree_root_id, promoted_page_id],
1121            };
1122
1123            let new_root_page_id = self.alloc_page_id();
1124            let new_root_page =
1125                Page::new(new_root_page_id, Node::FreeListInternal(new_internal_node));
1126            if verbose {
1127                println!(
1128                    "Created new internal root {:?}: {:?}",
1129                    new_root_page_id, new_root_page.node
1130                );
1131            }
1132            self.insert_dirty(new_root_page)?;
1133
1134            self.free_lists_tree_root_id = new_root_page_id;
1135        }
1136
1137        Ok(())
1138    }
1139
1140    /// Insert a PageID into the TSN-subtree rooted at `root_id`, maintaining sorted order.
1141    /// Returns the root id (maybe the same or a new root if promoted).
1142    fn tsn_subtree_insert(
1143        &mut self,
1144        mvcc: &Mvcc,
1145        root_id: PageID,
1146        key: PageID,
1147    ) -> DCBResult<PageID> {
1148        let verbose = self.verbose;
1149        let mut stack: Vec<(PageID, usize)> = Vec::new();
1150        let mut current_id = root_id;
1151        loop {
1152            let current_page_ref = self.get_page_ref(mvcc, current_id)?;
1153            match &current_page_ref.node {
1154                Node::FreeListTsnLeaf(_) => break,
1155                Node::FreeListTsnInternal(internal) => {
1156                    let child_idx = match internal.keys.binary_search_by(|k| k.0.cmp(&key.0)) {
1157                        Ok(idx) => idx + 1, // on equal, go right (matches existing >= loop)
1158                        Err(idx) => idx,    // first separator greater than key
1159                    };
1160                    let next_id = internal.child_ids[child_idx];
1161                    stack.push((current_id, child_idx));
1162                    current_id = next_id;
1163                }
1164                other => {
1165                    return Err(DCBError::DatabaseCorrupted(format!(
1166                        "Unexpected node type in TSN-subtree during insert: {}",
1167                        other.type_name()
1168                    )));
1169                }
1170            }
1171        }
1172
1173        // Please note, all pages will be already dirty, because all freed PageIDs for a TSN
1174        // are inserted in the same transaction, so we don't need to replace any CoW PageIDs.
1175        {
1176            let leaf_page = self.get_mut_dirty(current_id)?;
1177            let Node::FreeListTsnLeaf(ref mut leaf) = leaf_page.node else {
1178                return Err(DCBError::DatabaseCorrupted(
1179                    "Expected TSN-subtree leaf".to_string(),
1180                ));
1181            };
1182            // Binary search by key
1183            match leaf.page_ids.binary_search_by(|pid| pid.0.cmp(&key.0)) {
1184                Ok(_) => {
1185                    // Duplicate; nothing to do
1186                    if verbose {
1187                        println!("Duplicate PageID {:?} ignored in TSN-subtree", key);
1188                    }
1189                }
1190                Err(ins) => {
1191                    leaf.page_ids.insert(ins, key);
1192                    if leaf.calc_serialized_size() <= mvcc.max_node_size {
1193                        return Ok(root_id);
1194                    }
1195                    // Split the leaf in half without cloning the entire vector.
1196                    let mid = leaf.page_ids.len() / 2; // left gets floor(n/2), right gets ceil(n/2)
1197                    let right_ids: Vec<PageID> = leaf.page_ids.split_off(mid);
1198                    let promoted_key = right_ids[0];
1199                    // Create right leaf page
1200                    let right_leaf_id = self.alloc_page_id();
1201                    let right_leaf_node = FreeListTsnLeafNode {
1202                        page_ids: right_ids,
1203                    };
1204                    let right_leaf_page =
1205                        Page::new(right_leaf_id, Node::FreeListTsnLeaf(right_leaf_node));
1206                    self.insert_dirty(right_leaf_page)?;
1207                    // Propagate to parents
1208                    let mut promoted: Option<(PageID, PageID)> =
1209                        Some((promoted_key, right_leaf_id));
1210                    // Walk up the path
1211                    for (parent_id, child_idx) in stack.into_iter().rev() {
1212                        if let Some((prom_key, prom_right_id)) = promoted.take() {
1213                            let parent_page = self.get_mut_dirty(parent_id)?;
1214                            let Node::FreeListTsnInternal(ref mut parent_node) = parent_page.node
1215                            else {
1216                                return Err(DCBError::DatabaseCorrupted(
1217                                    "Expected TSN-subtree internal".to_string(),
1218                                ));
1219                            };
1220
1221                            // Insert promoted key and child at child_idx
1222                            parent_node.keys.insert(child_idx, prom_key);
1223                            parent_node.child_ids.insert(child_idx + 1, prom_right_id);
1224                            if parent_node.calc_serialized_size() <= mvcc.max_node_size {
1225                                // Fits; continue upward, no further promotion from this parent
1226                                current_id = parent_id;
1227                                continue;
1228                            }
1229                            // Overflow: split parent by midpoint and promote the right-min key
1230                            let total_keys = parent_node.keys.len();
1231                            debug_assert!(
1232                                total_keys >= 2,
1233                                "splitting parent with <2 keys after insert"
1234                            );
1235                            let mid = total_keys / 2; // left = 0..mid, promote = mid, right = mid+1..
1236                            let promote_up_key = parent_node.keys[mid];
1237                            // Build left side in place
1238                            let left_keys: Vec<PageID> = parent_node.keys[..mid].to_vec();
1239                            let left_child_ids: Vec<PageID> =
1240                                parent_node.child_ids[..=mid].to_vec();
1241                            // Build right side
1242                            let right_keys: Vec<PageID> = parent_node.keys[mid + 1..].to_vec();
1243                            let right_child_ids: Vec<PageID> =
1244                                parent_node.child_ids[mid + 1..].to_vec();
1245                            if right_child_ids.len() != right_keys.len() + 1 {
1246                                return Err(DCBError::DatabaseCorrupted(
1247                                    "TSN-subtree internal split produced invalid right arity"
1248                                        .to_string(),
1249                                ));
1250                            }
1251                            if left_child_ids.len() != left_keys.len() + 1 {
1252                                return Err(DCBError::DatabaseCorrupted(
1253                                    "TSN-subtree internal split produced invalid left arity"
1254                                        .to_string(),
1255                                ));
1256                            }
1257                            // Rewrite left into parent
1258                            parent_node.keys = left_keys;
1259                            parent_node.child_ids = left_child_ids;
1260                            // Create right internal node
1261                            let right_internal_id = self.alloc_page_id();
1262                            let right_internal =
1263                                crate::free_lists_tree_nodes::FreeListTsnInternalNode {
1264                                    keys: right_keys,
1265                                    child_ids: right_child_ids,
1266                                };
1267                            let right_internal_page = Page::new(
1268                                right_internal_id,
1269                                Node::FreeListTsnInternal(right_internal),
1270                            );
1271                            self.insert_dirty(right_internal_page)?;
1272                            // Set promoted to propagate upward
1273                            promoted = Some((promote_up_key, right_internal_id));
1274                        }
1275                        current_id = parent_id;
1276                    }
1277                    // If a promotion remains after processing all parents, create new root
1278                    if let Some((prom_key, prom_right_id)) = promoted.take() {
1279                        let new_root_id = self.alloc_page_id();
1280                        let left_id = current_id;
1281                        let new_root = crate::free_lists_tree_nodes::FreeListTsnInternalNode {
1282                            keys: vec![prom_key],
1283                            child_ids: vec![left_id, prom_right_id],
1284                        };
1285                        let new_root_page =
1286                            Page::new(new_root_id, Node::FreeListTsnInternal(new_root));
1287                        self.insert_dirty(new_root_page)?;
1288                        if verbose {
1289                            println!("Promoted new TSN-subtree root {:?}", new_root_id);
1290                        }
1291                        return Ok(new_root_id);
1292                    }
1293                }
1294            };
1295            Ok(root_id)
1296        }
1297    }
1298
1299    pub fn remove_free_page_id(
1300        &mut self,
1301        mvcc: &Mvcc,
1302        tsn: Tsn,
1303        used_page_id: PageID,
1304    ) -> DCBResult<()> {
1305        let verbose = self.verbose;
1306        if verbose {
1307            println!();
1308            println!("Removing {used_page_id:?} from {tsn:?}...");
1309            println!("Root is {:?}", self.free_lists_tree_root_id);
1310        }
1311        // Get the root page
1312        let mut current_page_id = self.free_lists_tree_root_id;
1313
1314        // Traverse the tree to find a leaf node
1315        let mut stack: Vec<PageID> = Vec::new();
1316        let mut removed_page_ids: Vec<PageID> = Vec::new();
1317
1318        loop {
1319            let current_page_ref = self.get_page_ref(mvcc, current_page_id)?;
1320            if matches!(current_page_ref.node, Node::FreeListLeaf(_)) {
1321                break;
1322            }
1323            if let Node::FreeListInternal(internal_node) = &current_page_ref.node {
1324                if verbose {
1325                    println!("Page {:?} is internal node", current_page_ref.page_id);
1326                }
1327                stack.push(current_page_id);
1328                current_page_id = *internal_node.child_ids.first().unwrap();
1329            } else {
1330                return Err(DCBError::DatabaseCorrupted(
1331                    "Expected FreeListInternal node".to_string(),
1332                ));
1333            }
1334        }
1335        if verbose {
1336            println!("Page {current_page_id:?} is leaf node");
1337        }
1338
1339        // We'll defer making the main leaf page dirty until we know we need to mutate it,
1340        // to avoid overlapping mutable borrows when also mutating the TSN-subtree.
1341        let mut replacement_info: Option<(PageID, PageID)> = None;
1342        let mut removal_info = None;
1343
1344        // Read the current leaf immutably to decide the path (inline vs TSN-subtree)
1345        let leaf_snapshot = { self.get_page_ref(mvcc, current_page_id)? };
1346        let Node::FreeListLeaf(leaf_node_ro) = &leaf_snapshot.node else {
1347            return Err(DCBError::DatabaseCorrupted(
1348                "Expected FreeListLeaf node".to_string(),
1349            ));
1350        };
1351        if leaf_node_ro.keys.is_empty() || leaf_node_ro.keys[0] != tsn {
1352            return Err(DCBError::DatabaseCorrupted(format!(
1353                "Expected TSN {} not found: {:?}",
1354                tsn.0, leaf_node_ro
1355            )));
1356        }
1357
1358        let leaf_value_root_id = leaf_node_ro.values[0].root_id;
1359        // let mut leaf_inline_page_ids: Option<Vec<PageID>> = None;
1360        // if leaf_value_root_id == PageID(0) {
1361        //     leaf_inline_page_ids = Some(leaf_node_ro.values[0].page_ids.clone());
1362        // }
1363
1364        if leaf_value_root_id == PageID(0) {
1365            // Inline list case: make the leaf dirty and remove from inline list
1366            let dirty_page_id = { self.get_dirty_page_id(current_page_id)? };
1367            if dirty_page_id != current_page_id {
1368                replacement_info = Some((current_page_id, dirty_page_id));
1369            }
1370            let dirty_leaf_page = self.get_mut_dirty(dirty_page_id)?;
1371            if let Node::FreeListLeaf(dirty_leaf_node) = &mut dirty_leaf_page.node {
1372                let leaf_value = &mut dirty_leaf_node.values[0];
1373                if let Some(pos) = leaf_value
1374                    .page_ids
1375                    .iter()
1376                    .position(|&id| id == used_page_id)
1377                {
1378                    leaf_value.page_ids.remove(pos);
1379                } else {
1380                    return Err(DCBError::DatabaseCorrupted(format!(
1381                        "{used_page_id:?} not found in {tsn:?}"
1382                    )));
1383                }
1384                if verbose {
1385                    println!("Removed {used_page_id:?} from {tsn:?} in {dirty_page_id:?}");
1386                }
1387                if leaf_value.page_ids.is_empty() {
1388                    dirty_leaf_node.keys.remove(0);
1389                    dirty_leaf_node.values.remove(0);
1390                    if verbose {
1391                        println!("Removed {tsn:?} from {dirty_page_id:?}");
1392                    }
1393                    if dirty_leaf_node.keys.is_empty() {
1394                        if verbose {
1395                            println!("Empty leaf page {dirty_page_id:?}: {dirty_leaf_node:?}");
1396                        }
1397                        removal_info = Some(dirty_page_id);
1398                    } else if verbose {
1399                        println!("Leaf page not empty {dirty_page_id:?}: {dirty_leaf_node:?}");
1400                    }
1401                } else if verbose {
1402                    println!("Leaf value not empty {tsn:?}: {leaf_value:?}");
1403                }
1404            } else {
1405                return Err(DCBError::DatabaseCorrupted(
1406                    "Expected FreeListLeaf node".to_string(),
1407                ));
1408            }
1409        } else {
1410            // TSN-subtree case: mutate the TSN leaf first
1411            let tsn_root_id = leaf_value_root_id;
1412            let dirty_tsn_root_id = { self.get_dirty_page_id(tsn_root_id)? };
1413            let mut tsn_root_replaced: Option<PageID> = None;
1414            let dirty_tsn_root_page = self.get_mut_dirty(dirty_tsn_root_id)?;
1415            let mut tsn_leaf_became_empty = false;
1416            let mut tsn_child_leaf_became_empty = false;
1417            match &mut dirty_tsn_root_page.node {
1418                Node::FreeListTsnLeaf(tsn_leaf_node) => {
1419                    if let Some(pos) = tsn_leaf_node
1420                        .page_ids
1421                        .iter()
1422                        .position(|&id| id == used_page_id)
1423                    {
1424                        tsn_leaf_node.page_ids.remove(pos);
1425                    } else {
1426                        return Err(DCBError::DatabaseCorrupted(format!(
1427                            "{used_page_id:?} not found in TSN-subtree for {tsn:?}"
1428                        )));
1429                    }
1430                    if verbose {
1431                        println!(
1432                            "Removed {used_page_id:?} from TSN-subtree leaf {dirty_tsn_root_id:?} for {tsn:?}"
1433                        );
1434                    }
1435                    if tsn_leaf_node.page_ids.is_empty() {
1436                        tsn_leaf_became_empty = true;
1437                        removed_page_ids.push(dirty_tsn_root_id);
1438                    }
1439                }
1440                Node::FreeListTsnInternal(_) => {
1441                    // General TSN-subtree removal with arbitrary depth. We will:
1442                    // 1) Descend using separator keys to locate the target leaf containing used_page_id.
1443                    // 2) Make a copy-on-write path from root to that leaf, updating parent->child links
1444                    //    if any node gets a new dirty page id.
1445                    // 3) Remove the page id from the leaf. If the leaf becomes empty, remove the child
1446                    //    from its parent, adjust separator keys, and if the internal ends up with a
1447                    //    single child, promote that child. Propagate promotions up as needed.
1448
1449                    // Build the path of (internal_id, child_index_chosen) down to the leaf.
1450                    let mut path: Vec<(PageID, usize)> = Vec::new();
1451                    let mut current_id = dirty_tsn_root_id;
1452                    loop {
1453                        let node_owned = { self.get_page_ref(mvcc, current_id)?.node.clone() };
1454                        match node_owned {
1455                            Node::FreeListTsnLeaf(_) => {
1456                                break; // current_id is the leaf
1457                            }
1458                            Node::FreeListTsnInternal(internal) => {
1459                                // Choose child index by separator keys: count keys <= used_page_id
1460                                let mut child_idx = 0usize;
1461                                while child_idx < internal.keys.len()
1462                                    && used_page_id >= internal.keys[child_idx]
1463                                {
1464                                    child_idx += 1;
1465                                }
1466                                let next_id = internal.child_ids[child_idx];
1467                                path.push((current_id, child_idx));
1468                                current_id = next_id;
1469                            }
1470                            other => {
1471                                return Err(DCBError::DatabaseCorrupted(format!(
1472                                    "Unexpected node type in TSN-subtree during descent: {}",
1473                                    other.type_name()
1474                                )));
1475                            }
1476                        }
1477                    }
1478
1479                    // Now current_id points to the target leaf. Make it dirty and remove the page id.
1480                    let mut dirty_child_id = { self.get_dirty_page_id(current_id)? };
1481                    {
1482                        let child_page = self.get_mut_dirty(dirty_child_id)?;
1483                        match &mut child_page.node {
1484                            Node::FreeListTsnLeaf(leaf_node) => {
1485                                if let Some(pos) =
1486                                    leaf_node.page_ids.iter().position(|&id| id == used_page_id)
1487                                {
1488                                    leaf_node.page_ids.remove(pos);
1489                                } else {
1490                                    return Err(DCBError::DatabaseCorrupted(format!(
1491                                        "{used_page_id:?} not found in TSN-subtree for {tsn:?}"
1492                                    )));
1493                                }
1494                                if verbose {
1495                                    println!(
1496                                        "Removed {used_page_id:?} from TSN-subtree leaf {dirty_child_id:?} for {tsn:?}"
1497                                    );
1498                                }
1499                                if leaf_node.page_ids.is_empty() {
1500                                    tsn_child_leaf_became_empty = true;
1501                                    removed_page_ids.push(dirty_child_id);
1502                                }
1503                            }
1504                            other => {
1505                                return Err(DCBError::DatabaseCorrupted(format!(
1506                                    "Expected TSN-subtree leaf, got {}",
1507                                    other.type_name()
1508                                )));
1509                            }
1510                        }
1511                    }
1512
1513                    // Walk back up the path, ensuring parents are dirty and applying updates.
1514                    let mut subtree_emptied = false;
1515                    let mut new_root_id_opt: Option<PageID> = None;
1516
1517                    let path_len = path.len();
1518                    for (level, (parent_id, child_idx)) in path.into_iter().rev().enumerate() {
1519                        // Make parent dirty (COW) if needed
1520                        let parent_dirty_id = { self.get_dirty_page_id(parent_id)? };
1521
1522                        // If parent was COW-ed, its own parent (next iteration) must update pointer to it.
1523                        // We achieve this by propagating parent_dirty_id via dirty_child_id variable at end.
1524
1525                        // Mutate the parent
1526                        let parent_page = self.get_mut_dirty(parent_dirty_id)?;
1527                        let Node::FreeListTsnInternal(ref mut parent_node) = parent_page.node
1528                        else {
1529                            return Err(DCBError::DatabaseCorrupted(
1530                                "Expected TSN-subtree internal node".to_string(),
1531                            ));
1532                        };
1533
1534                        if tsn_child_leaf_became_empty && level == 0 {
1535                            // Remove the empty leaf child from this parent
1536                            parent_node.child_ids.remove(child_idx);
1537                            if !parent_node.keys.is_empty() {
1538                                let key_remove_idx = if child_idx == 0 { 0 } else { child_idx - 1 };
1539                                if key_remove_idx < parent_node.keys.len() {
1540                                    parent_node.keys.remove(key_remove_idx);
1541                                }
1542                            }
1543
1544                            // After removal, decide on collapse/promotion
1545                            match parent_node.child_ids.len() {
1546                                0 => {
1547                                    // Whole TSN-subtree emptied
1548                                    subtree_emptied = true;
1549                                    removed_page_ids.push(parent_dirty_id);
1550                                }
1551                                1 => {
1552                                    // Promote sole remaining child
1553                                    let remaining_child = parent_node.child_ids[0];
1554                                    removed_page_ids.push(parent_dirty_id);
1555                                    // For upper level (or main leaf if this was the root), this node is replaced by remaining_child
1556                                    dirty_child_id = remaining_child;
1557                                    new_root_id_opt = Some(remaining_child);
1558                                }
1559                                _ => {
1560                                    // Parent remains; propagate its (possibly COW-ed) id upward
1561                                    dirty_child_id = parent_dirty_id;
1562                                    if level == path_len - 1 {
1563                                        // If this was the root parent (topmost), remember it as new root when applicable
1564                                        new_root_id_opt = Some(parent_dirty_id);
1565                                    }
1566                                }
1567                            }
1568                        } else {
1569                            // Child was not removed; update the pointer if it changed due to COW
1570                            if parent_node.child_ids[child_idx] != dirty_child_id {
1571                                parent_node.child_ids[child_idx] = dirty_child_id;
1572                            }
1573                            // Propagate upward: the current parent may itself have been COW-ed
1574                            dirty_child_id = parent_dirty_id;
1575                            if level == path_len - 1 {
1576                                new_root_id_opt = Some(parent_dirty_id);
1577                            }
1578                        }
1579                    }
1580
1581                    // Determine if the TSN-subtree root changed. Prefer promotion result if set; otherwise, if
1582                    // root was COW-ed or updated, use new_root_id_opt.
1583                    if subtree_emptied {
1584                        tsn_leaf_became_empty = true;
1585                    } else if let Some(new_root) = new_root_id_opt
1586                        && new_root != dirty_tsn_root_id
1587                    {
1588                        tsn_root_replaced = Some(new_root);
1589                    }
1590                }
1591                _ => {
1592                    return Err(DCBError::DatabaseCorrupted(
1593                        "Expected TSN-subtree node".to_string(),
1594                    ));
1595                }
1596            }
1597
1598            // Now mutate the main leaf as needed
1599            let dirty_page_id = { self.get_dirty_page_id(current_page_id)? };
1600            if dirty_page_id != current_page_id {
1601                replacement_info = Some((current_page_id, dirty_page_id));
1602            }
1603            let dirty_leaf_page = self.get_mut_dirty(dirty_page_id)?;
1604            if let Node::FreeListLeaf(dirty_leaf_node) = &mut dirty_leaf_page.node {
1605                // Ensure expected TSN still at index 0
1606                if dirty_leaf_node.keys.is_empty() || dirty_leaf_node.keys[0] != tsn {
1607                    return Err(DCBError::DatabaseCorrupted(format!(
1608                        "Expected TSN {} not found in dirty leaf: {:?}",
1609                        tsn.0, dirty_leaf_node
1610                    )));
1611                }
1612                if tsn_leaf_became_empty {
1613                    // Remove the TSN entry entirely
1614                    dirty_leaf_node.keys.remove(0);
1615                    dirty_leaf_node.values.remove(0);
1616                    if verbose {
1617                        println!("Removed {tsn:?} from {dirty_page_id:?}");
1618                    }
1619                    if dirty_leaf_node.keys.is_empty() {
1620                        if verbose {
1621                            println!("Empty leaf page {dirty_page_id:?}: {dirty_leaf_node:?}");
1622                        }
1623                        removal_info = Some(dirty_page_id);
1624                    } else if verbose {
1625                        println!("Leaf page not empty {dirty_page_id:?}: {dirty_leaf_node:?}");
1626                    }
1627                } else if let Some(new_root) = tsn_root_replaced {
1628                    // TSN-subtree root changed (internal collapsed to single child)
1629                    dirty_leaf_node.values[0].root_id = new_root;
1630                } else if dirty_tsn_root_id != tsn_root_id {
1631                    // Update the pointer to the new dirty TSN leaf (COW of root leaf)
1632                    dirty_leaf_node.values[0].root_id = dirty_tsn_root_id;
1633                }
1634            } else {
1635                return Err(DCBError::DatabaseCorrupted(
1636                    "Expected FreeListLeaf node".to_string(),
1637                ));
1638            }
1639        }
1640
1641        // Propagate replacements and removals up the stack
1642        let mut current_replacement_info = replacement_info;
1643
1644        while let Some(parent_page_id) = stack.pop() {
1645            // Make the internal page dirty
1646            let dirty_page_id = { self.get_dirty_page_id(parent_page_id)? };
1647            let parent_replacement_info: Option<(PageID, PageID)> = {
1648                if dirty_page_id != parent_page_id {
1649                    Some((parent_page_id, dirty_page_id))
1650                } else {
1651                    None
1652                }
1653            };
1654            // Get a mutable internal node....
1655            let dirty_internal_page = self.get_mut_dirty(dirty_page_id)?;
1656
1657            if let Some((old_id, new_id)) = current_replacement_info {
1658                if let Node::FreeListInternal(dirty_internal_node) = &mut dirty_internal_page.node {
1659                    // Replace the child ID
1660                    if dirty_internal_node.child_ids[0] == old_id {
1661                        dirty_internal_node.child_ids[0] = new_id;
1662                        if verbose {
1663                            println!(
1664                                "Replaced {old_id:?} with {new_id:?} in {dirty_page_id:?}: {dirty_internal_page:?}"
1665                            );
1666                        }
1667                    } else {
1668                        return Err(DCBError::DatabaseCorrupted("Child ID mismatch".to_string()));
1669                    }
1670                } else {
1671                    return Err(DCBError::DatabaseCorrupted(
1672                        "Expected FreeListInternal node".to_string(),
1673                    ));
1674                }
1675            }
1676            current_replacement_info = parent_replacement_info;
1677
1678            if let Some(removed_page_id) = removal_info {
1679                removed_page_ids.push(removed_page_id);
1680
1681                if let Node::FreeListInternal(dirty_internal_node) = &mut dirty_internal_page.node {
1682                    // Remove the child ID and key
1683                    if dirty_internal_node.child_ids[0] != removed_page_id {
1684                        return Err(DCBError::DatabaseCorrupted("Child ID mismatch".to_string()));
1685                    }
1686                    if dirty_internal_node.keys.is_empty() {
1687                        return Err(DCBError::DatabaseCorrupted(
1688                            "Empty internal node keys".to_string(),
1689                        ));
1690                    }
1691                    dirty_internal_node.child_ids.remove(0);
1692                    dirty_internal_node.keys.remove(0);
1693                    if verbose {
1694                        println!(
1695                            "Removed {removed_page_id:?} from {dirty_page_id:?}: {dirty_internal_node:?}"
1696                        );
1697                    }
1698
1699                    // If the internal node is empty or has only one child, mark it for removal
1700                    if dirty_internal_node.keys.is_empty() {
1701                        if verbose {
1702                            println!(
1703                                "Empty internal page {dirty_page_id:?}: {dirty_internal_node:?}"
1704                            );
1705                        }
1706                        assert_eq!(dirty_internal_node.child_ids.len(), 1);
1707                        let orphaned_child_id = dirty_internal_node.child_ids[0];
1708
1709                        removed_page_ids.push(dirty_page_id);
1710
1711                        if let Some((old_id, _)) = parent_replacement_info {
1712                            current_replacement_info = Some((old_id, orphaned_child_id));
1713                        } else {
1714                            current_replacement_info = Some((dirty_page_id, orphaned_child_id));
1715                        }
1716                    }
1717                } else {
1718                    return Err(DCBError::DatabaseCorrupted(
1719                        "Expected FreeListInternal node".to_string(),
1720                    ));
1721                }
1722
1723                removal_info = None;
1724            }
1725        }
1726
1727        for &removed_page_id in &removed_page_ids {
1728            self.append_freed_page_id(removed_page_id);
1729        }
1730
1731        // Update the free_lists_tree_root_id if needed (it might already have been
1732        // updated with the page ID of a dirty page.
1733        if let Some((old_id, new_id)) = current_replacement_info {
1734            if self.free_lists_tree_root_id == old_id {
1735                self.append_freed_page_id(old_id);
1736                self.free_lists_tree_root_id = new_id;
1737                if verbose {
1738                    println!("Replaced root {old_id:?} with {new_id:?}");
1739                }
1740            } else {
1741                return Err(DCBError::RootIDMismatch(old_id.0, new_id.0));
1742            }
1743        }
1744
1745        Ok(())
1746    }
1747}
1748
1749enum FreePageIDInsertStrategy {
1750    PushTsnOntoFreeListLeaf,
1751    PushPageIdOntoFreeListLeaf(usize),
1752    PushPageIdOntoExistingTsnSubtree,
1753    MoveTsnToNewTsnSubtree,
1754    SplitFreeListLeaf,
1755    CreateAndPromoteFreeListLeaf,
1756}
1757
1758// Reader transaction
1759pub struct Reader {
1760    pub header_page_id: PageID,
1761    pub tsn: Tsn,
1762    pub events_tree_root_id: PageID,
1763    pub tags_tree_root_id: PageID,
1764    pub next_position: Position,
1765    reader_id: usize,
1766    reader_tsns: Arc<DashMap<usize, Tsn>>,
1767}
1768
1769impl Drop for Reader {
1770    fn drop(&mut self) {
1771        // Remove reader TSN from the concurrent map (lock-free)
1772        self.reader_tsns.remove(&self.reader_id);
1773    }
1774}
1775
1776#[cfg(test)]
1777mod tests {
1778    use super::*;
1779    use crate::free_lists_tree_nodes::FreeListLeafValue;
1780    use serial_test::serial;
1781    use tempfile::tempdir;
1782
1783    static VERBOSE: bool = false;
1784
1785    #[test]
1786    #[serial]
1787    fn test_mvcc_init() {
1788        let temp_dir = tempdir().unwrap();
1789        let db_path = temp_dir.path().join("mvcc-test.db");
1790
1791        {
1792            let db = Mvcc::new(&db_path, 4096, VERBOSE).unwrap();
1793            assert!(db.pager.is_file_new);
1794        }
1795
1796        {
1797            let db = Mvcc::new(&db_path, 4096, VERBOSE).unwrap();
1798            assert!(!db.pager.is_file_new);
1799        }
1800    }
1801
1802    #[test]
1803    #[serial]
1804    fn test_write_transaction_incrementing_tsn_and_alternating_header() {
1805        let temp_dir = tempdir().unwrap();
1806        let db_path = temp_dir.path().join("mvcc-test.db");
1807        let db = Mvcc::new(&db_path, 4096, VERBOSE).unwrap();
1808
1809        {
1810            let mut writer = db.writer().unwrap();
1811            assert_eq!(Tsn(1), writer.tsn);
1812            assert_eq!(PageID(0), writer.header_page_id);
1813            db.commit(&mut writer).unwrap();
1814        }
1815
1816        {
1817            let mut writer = db.writer().unwrap();
1818            assert_eq!(Tsn(2), writer.tsn);
1819            assert_eq!(PageID(1), writer.header_page_id);
1820            db.commit(&mut writer).unwrap();
1821        }
1822
1823        {
1824            let mut writer = db.writer().unwrap();
1825            assert_eq!(Tsn(3), writer.tsn);
1826            assert_eq!(PageID(0), writer.header_page_id);
1827            db.commit(&mut writer).unwrap();
1828        }
1829
1830        {
1831            let mut writer = db.writer().unwrap();
1832            assert_eq!(Tsn(4), writer.tsn);
1833            assert_eq!(PageID(1), writer.header_page_id);
1834            db.commit(&mut writer).unwrap();
1835        }
1836
1837        {
1838            let mut writer = db.writer().unwrap();
1839            assert_eq!(Tsn(5), writer.tsn);
1840            assert_eq!(PageID(0), writer.header_page_id);
1841            db.commit(&mut writer).unwrap();
1842        }
1843    }
1844
1845    #[test]
1846    #[serial]
1847    fn test_read_transaction_header_and_tsn() {
1848        let temp_dir = tempdir().unwrap();
1849        let db_path = temp_dir.path().join("mvcc-test.db");
1850        let db = Mvcc::new(&db_path, 4096, VERBOSE).unwrap();
1851
1852        // Initial reader should see TSN 0
1853        {
1854            assert_eq!(0, db.reader_tsns.len());
1855            let reader = db.reader().unwrap();
1856            assert_eq!(1, db.reader_tsns.len());
1857            assert_eq!(
1858                vec![Tsn(0)],
1859                db.reader_tsns
1860                    .iter()
1861                    .map(|r| *r.value())
1862                    .collect::<Vec<_>>()
1863            );
1864            assert_eq!(PageID(0), reader.header_page_id);
1865            assert_eq!(Tsn(0), reader.tsn);
1866        }
1867        assert_eq!(0, db.reader_tsns.len());
1868
1869        // Multiple nested readers
1870        {
1871            let reader1 = db.reader().unwrap();
1872            assert_eq!(
1873                vec![Tsn(0)],
1874                db.reader_tsns
1875                    .iter()
1876                    .map(|r| *r.value())
1877                    .collect::<Vec<_>>()
1878            );
1879            assert_eq!(PageID(0), reader1.header_page_id);
1880            assert_eq!(Tsn(0), reader1.tsn);
1881
1882            {
1883                let reader2 = db.reader().unwrap();
1884                assert_eq!(
1885                    vec![Tsn(0), Tsn(0)],
1886                    db.reader_tsns
1887                        .iter()
1888                        .map(|r| *r.value())
1889                        .collect::<Vec<_>>()
1890                );
1891                assert_eq!(PageID(0), reader2.header_page_id);
1892                assert_eq!(Tsn(0), reader2.tsn);
1893
1894                {
1895                    let reader3 = db.reader().unwrap();
1896                    assert_eq!(
1897                        vec![Tsn(0), Tsn(0), Tsn(0)],
1898                        db.reader_tsns
1899                            .iter()
1900                            .map(|r| *r.value())
1901                            .collect::<Vec<_>>()
1902                    );
1903                    assert_eq!(PageID(0), reader3.header_page_id);
1904                    assert_eq!(Tsn(0), reader3.tsn);
1905                }
1906            }
1907        }
1908        assert_eq!(0, db.reader_tsns.len());
1909
1910        // Writer transaction
1911        {
1912            let mut writer = db.writer().unwrap();
1913            assert_eq!(0, db.reader_tsns.len());
1914            assert_eq!(Tsn(1), writer.tsn);
1915            assert_eq!(PageID(0), writer.header_page_id);
1916            db.commit(&mut writer).unwrap();
1917        }
1918
1919        // Reader after writer
1920        {
1921            let reader = db.reader().unwrap();
1922            assert_eq!(
1923                vec![Tsn(1)],
1924                db.reader_tsns
1925                    .iter()
1926                    .map(|r| *r.value())
1927                    .collect::<Vec<_>>()
1928            );
1929            assert_eq!(PageID(1), reader.header_page_id);
1930            assert_eq!(Tsn(1), reader.tsn);
1931        }
1932    }
1933
1934    #[test]
1935    #[serial]
1936    fn test_copy_on_write_page_reuse() {
1937        let temp_dir = tempdir().unwrap();
1938        let db_path = temp_dir.path().join("mvcc-test.db");
1939        let db = Mvcc::new(&db_path, 4096, VERBOSE).unwrap();
1940        // First transaction
1941        {
1942            let mut writer = db.writer().unwrap();
1943
1944            // Check there are no free pages
1945            assert_eq!(0, writer.reusable_page_ids.len());
1946
1947            // Check the free list root is page 2
1948            assert_eq!(PageID(2), writer.free_lists_tree_root_id);
1949
1950            // Check the position root is page 3
1951            assert_eq!(PageID(3), writer.events_tree_root_id);
1952
1953            // Allocate and insert free page ID.
1954            let free_page_id = writer.alloc_page_id();
1955            assert_eq!(PageID(5), free_page_id);
1956            writer
1957                .insert_freed_page_id(&db, writer.tsn, free_page_id)
1958                .unwrap();
1959
1960            // Check the dirty page IDs
1961            assert_eq!(1, writer.dirty.len());
1962            assert_eq!(PageID(6), *writer.dirty.keys().collect::<Vec<_>>()[0]);
1963
1964            // Check the freed page IDs
1965            assert_eq!(1, writer.freed_page_ids.len());
1966            assert_eq!(PageID(2), writer.freed_page_ids[0]);
1967
1968            db.commit(&mut writer).unwrap();
1969        }
1970
1971        // Second transaction
1972        {
1973            let mut writer = db.writer().unwrap();
1974
1975            // Check there are two free pages
1976            assert_eq!(2, writer.reusable_page_ids.len());
1977            assert_eq!((PageID(5), Tsn(1)), writer.reusable_page_ids[0]);
1978            assert_eq!((PageID(2), Tsn(1)), writer.reusable_page_ids[1]);
1979
1980            // Check the free list root is page 2
1981            assert_eq!(PageID(6), writer.free_lists_tree_root_id);
1982
1983            // Check the position root is page 3
1984            assert_eq!(PageID(3), writer.events_tree_root_id);
1985
1986            // Allocate and insert free page ID.
1987            let free_page_id = writer.alloc_page_id();
1988            assert_eq!(PageID(5), free_page_id);
1989            writer
1990                .insert_freed_page_id(&db, writer.tsn, free_page_id)
1991                .unwrap();
1992
1993            // Check the dirty page IDs
1994            assert_eq!(1, writer.dirty.len());
1995            assert_eq!(PageID(2), *writer.dirty.keys().collect::<Vec<_>>()[0]);
1996
1997            // Check the freed page IDs
1998            assert_eq!(1, writer.freed_page_ids.len());
1999            assert_eq!(PageID(6), writer.freed_page_ids[0]);
2000
2001            db.commit(&mut writer).unwrap();
2002        }
2003
2004        // Third transaction
2005        {
2006            let mut writer = db.writer().unwrap();
2007
2008            // Check there are two free pages
2009            assert_eq!(2, writer.reusable_page_ids.len());
2010            assert_eq!((PageID(5), Tsn(2)), writer.reusable_page_ids[0]);
2011            assert_eq!((PageID(6), Tsn(2)), writer.reusable_page_ids[1]);
2012
2013            // Check the free list root is page 2
2014            assert_eq!(PageID(2), writer.free_lists_tree_root_id);
2015
2016            // Check the position root is page 3
2017            assert_eq!(PageID(3), writer.events_tree_root_id);
2018
2019            // Allocate and insert free page ID.
2020            let free_page_id = writer.alloc_page_id();
2021            assert_eq!(PageID(5), free_page_id);
2022            writer
2023                .insert_freed_page_id(&db, writer.tsn, free_page_id)
2024                .unwrap();
2025
2026            // Check the dirty page IDs
2027            assert_eq!(1, writer.dirty.len());
2028            assert_eq!(PageID(6), *writer.dirty.keys().collect::<Vec<_>>()[0]);
2029
2030            // Check the freed page IDs
2031            assert_eq!(1, writer.freed_page_ids.len());
2032            assert_eq!(PageID(2), writer.freed_page_ids[0]);
2033
2034            db.commit(&mut writer).unwrap();
2035        }
2036    }
2037
2038    // FreeListTree tests
2039    mod free_list_tree_tests {
2040        use super::*;
2041        use serial_test::serial;
2042        use tempfile::tempdir;
2043
2044        // Helper function to create a test database with a specified page size
2045        fn construct_mvcc(page_size: usize) -> (tempfile::TempDir, Mvcc) {
2046            let temp_dir = tempdir().unwrap();
2047            let db_path = temp_dir.path().join("mvcc-test.db");
2048            let db = Mvcc::new(&db_path, page_size, VERBOSE).unwrap();
2049            (temp_dir, db)
2050        }
2051
2052        #[test]
2053        #[serial]
2054        fn test_find_reusable_page_ids_empty_no_entries() {
2055            let (_temp_dir, db) = construct_mvcc(64);
2056            // New writer invokes find_reusable_page_ids in Mvcc::writer
2057            let writer = db.writer().unwrap();
2058            assert_eq!(0, writer.reusable_page_ids.len());
2059        }
2060
2061        #[test]
2062        #[serial]
2063        fn test_find_reusable_page_ids_leaf() {
2064            let (_temp_dir, db) = construct_mvcc(64);
2065            let mut writer = db.writer().unwrap();
2066
2067            let (tsn, free_pid1, free_pid2) = build_free_list_tree_leaf(&mut writer);
2068
2069            // Recompute
2070            writer.find_reusable_page_ids(&db).unwrap();
2071            assert_eq!(2, writer.reusable_page_ids.len());
2072            assert_eq!((free_pid1, tsn), writer.reusable_page_ids[0]);
2073            assert_eq!((free_pid2, tsn), writer.reusable_page_ids[1]);
2074        }
2075
2076        #[test]
2077        #[serial]
2078        fn test_find_reusable_page_ids_internal_leaf() {
2079            let (_temp_dir, db) = construct_mvcc(128);
2080            let mut writer = db.writer().unwrap();
2081
2082            let (tsn1, tsn2, pid1, pid2, pid3, pid4) =
2083                build_free_list_tree_internal_leaf(&mut writer);
2084
2085            // Recompute
2086            writer.find_reusable_page_ids(&db).unwrap();
2087            // Expect entries from leaf1 then leaf2
2088            assert_eq!(4, writer.reusable_page_ids.len());
2089            assert_eq!((pid1, tsn1), writer.reusable_page_ids[0]);
2090            assert_eq!((pid2, tsn1), writer.reusable_page_ids[1]);
2091            assert_eq!((pid3, tsn2), writer.reusable_page_ids[2]);
2092            assert_eq!((pid4, tsn2), writer.reusable_page_ids[3]);
2093        }
2094
2095        #[test]
2096        #[serial]
2097        fn test_find_reusable_page_ids_internal_internal_leaf() {
2098            let (_temp_dir, db) = construct_mvcc(128);
2099            let mut writer = db.writer().unwrap();
2100
2101            let (tsn1, tsn2, tsn3, tsn4, pid1, pid2, pid3, pid4, pid5, pid6, pid7, pid8) =
2102                build_free_list_tree_internal_internal_leaf(&mut writer);
2103
2104            // Recompute
2105            writer.find_reusable_page_ids(&db).unwrap();
2106            // Expect entries from leaf1 then leaf2
2107            assert_eq!(8, writer.reusable_page_ids.len());
2108            assert_eq!((pid1, tsn1), writer.reusable_page_ids[0]);
2109            assert_eq!((pid2, tsn1), writer.reusable_page_ids[1]);
2110            assert_eq!((pid3, tsn2), writer.reusable_page_ids[2]);
2111            assert_eq!((pid4, tsn2), writer.reusable_page_ids[3]);
2112            assert_eq!((pid5, tsn3), writer.reusable_page_ids[4]);
2113            assert_eq!((pid6, tsn3), writer.reusable_page_ids[5]);
2114            assert_eq!((pid7, tsn4), writer.reusable_page_ids[6]);
2115            assert_eq!((pid8, tsn4), writer.reusable_page_ids[7]);
2116        }
2117
2118        #[test]
2119        #[serial]
2120        fn test_find_reusable_page_ids_leaf_tsn_subtree_leaf() {
2121            let (_temp_dir, db) = construct_mvcc(64);
2122            let mut writer = db.writer().unwrap();
2123
2124            let (pid1, pid2, tsn) = build_free_list_tree_leaf_tsn_subtree_leaf(&mut writer);
2125
2126            // Recompute
2127            writer.find_reusable_page_ids(&db).unwrap();
2128            // Expect entries from leaf1 then leaf2
2129            assert_eq!(2, writer.reusable_page_ids.len());
2130            assert_eq!((pid1, tsn), writer.reusable_page_ids[0]);
2131            assert_eq!((pid2, tsn), writer.reusable_page_ids[1]);
2132        }
2133
2134        #[test]
2135        #[serial]
2136        fn test_find_reusable_page_ids_leaf_tsn_subtree_internal_leaf() {
2137            let (_temp_dir, db) = construct_mvcc(64);
2138            let mut writer = db.writer().unwrap();
2139
2140            let (pid1, pid2, pid3, pid4, tsn) =
2141                build_free_list_tree_leaf_tsn_subtree_internal_leaf(&mut writer);
2142
2143            // Recompute
2144            writer.find_reusable_page_ids(&db).unwrap();
2145            // Expect entries from leaf1 then leaf2
2146            assert_eq!(4, writer.reusable_page_ids.len());
2147            assert_eq!((pid1, tsn), writer.reusable_page_ids[0]);
2148            assert_eq!((pid2, tsn), writer.reusable_page_ids[1]);
2149            assert_eq!((pid3, tsn), writer.reusable_page_ids[2]);
2150            assert_eq!((pid4, tsn), writer.reusable_page_ids[3]);
2151        }
2152
2153        #[test]
2154        #[serial]
2155        fn test_find_reusable_page_ids_leaf_tsn_subtree_internal_internal_leaf() {
2156            let (_temp_dir, db) = construct_mvcc(64);
2157            let mut writer = db.writer().unwrap();
2158
2159            let (pid1, pid2, pid3, pid4, pid5, pid6, pid7, pid8, tsn) =
2160                build_free_list_tree_leaf_tsn_subtree_internal_internal_leaf(&mut writer);
2161
2162            // Recompute
2163            writer.find_reusable_page_ids(&db).unwrap();
2164            // Expect entries from leaf1 then leaf2
2165            assert_eq!(8, writer.reusable_page_ids.len());
2166            assert_eq!((pid1, tsn), writer.reusable_page_ids[0]);
2167            assert_eq!((pid2, tsn), writer.reusable_page_ids[1]);
2168            assert_eq!((pid3, tsn), writer.reusable_page_ids[2]);
2169            assert_eq!((pid4, tsn), writer.reusable_page_ids[3]);
2170            assert_eq!((pid5, tsn), writer.reusable_page_ids[4]);
2171            assert_eq!((pid6, tsn), writer.reusable_page_ids[5]);
2172            assert_eq!((pid7, tsn), writer.reusable_page_ids[6]);
2173            assert_eq!((pid8, tsn), writer.reusable_page_ids[7]);
2174        }
2175
2176        fn build_free_list_tree_leaf(writer: &mut Writer) -> (Tsn, PageID, PageID) {
2177            // Build a leaf node with one TSN and one PageID
2178            let tsn = Tsn(123);
2179            let free_pid1 = writer.alloc_page_id();
2180            let free_pid2 = writer.alloc_page_id();
2181            let leaf = FreeListLeafNode {
2182                keys: vec![tsn],
2183                values: vec![FreeListLeafValue {
2184                    page_ids: vec![free_pid1, free_pid2],
2185                    root_id: PageID(0),
2186                }],
2187            };
2188            let root_id = writer.alloc_page_id();
2189            let page = Page::new(root_id, Node::FreeListLeaf(leaf));
2190            writer.insert_dirty(page).unwrap();
2191            writer.append_freed_page_id(writer.free_lists_tree_root_id);
2192            writer.free_lists_tree_root_id = root_id;
2193            (tsn, free_pid1, free_pid2)
2194        }
2195
2196        fn build_free_list_tree_internal_leaf(
2197            writer: &mut Writer,
2198        ) -> (Tsn, Tsn, PageID, PageID, PageID, PageID) {
2199            // Two leaves each with one (TSN -> [PageID])
2200            let tsn1 = Tsn(10);
2201            let tsn2 = Tsn(20);
2202            let pid1 = writer.alloc_page_id();
2203            let pid2 = writer.alloc_page_id();
2204            let pid3 = writer.alloc_page_id();
2205            let pid4 = writer.alloc_page_id();
2206
2207            let leaf1_id = writer.alloc_page_id();
2208            let leaf2_id = writer.alloc_page_id();
2209            let leaf1 = FreeListLeafNode {
2210                keys: vec![tsn1],
2211                values: vec![FreeListLeafValue {
2212                    page_ids: vec![pid1, pid2],
2213                    root_id: PageID(0),
2214                }],
2215            };
2216            let leaf2 = FreeListLeafNode {
2217                keys: vec![tsn2],
2218                values: vec![FreeListLeafValue {
2219                    page_ids: vec![pid3, pid4],
2220                    root_id: PageID(0),
2221                }],
2222            };
2223            writer
2224                .insert_dirty(Page::new(leaf1_id, Node::FreeListLeaf(leaf1)))
2225                .unwrap();
2226            writer
2227                .insert_dirty(Page::new(leaf2_id, Node::FreeListLeaf(leaf2)))
2228                .unwrap();
2229
2230            // Internal root pointing to the two leaves (keys are not used by traversal here)
2231            let internal = FreeListInternalNode {
2232                keys: vec![tsn1],
2233                child_ids: vec![leaf1_id, leaf2_id],
2234            };
2235            let root_id = writer.alloc_page_id();
2236            writer
2237                .insert_dirty(Page::new(root_id, Node::FreeListInternal(internal)))
2238                .unwrap();
2239            writer.append_freed_page_id(writer.free_lists_tree_root_id);
2240            writer.free_lists_tree_root_id = root_id;
2241            (tsn1, tsn2, pid1, pid2, pid3, pid4)
2242        }
2243
2244        fn build_free_list_tree_internal_internal_leaf(
2245            writer: &mut Writer,
2246        ) -> (
2247            Tsn,
2248            Tsn,
2249            Tsn,
2250            Tsn,
2251            PageID,
2252            PageID,
2253            PageID,
2254            PageID,
2255            PageID,
2256            PageID,
2257            PageID,
2258            PageID,
2259        ) {
2260            // Two leaves each with one (TSN -> [PageID])
2261            let tsn1 = Tsn(10);
2262            let tsn2 = Tsn(20);
2263            let tsn3 = Tsn(30);
2264            let tsn4 = Tsn(40);
2265            let pid1 = writer.alloc_page_id();
2266            let pid2 = writer.alloc_page_id();
2267            let pid3 = writer.alloc_page_id();
2268            let pid4 = writer.alloc_page_id();
2269            let pid5 = writer.alloc_page_id();
2270            let pid6 = writer.alloc_page_id();
2271            let pid7 = writer.alloc_page_id();
2272            let pid8 = writer.alloc_page_id();
2273
2274            let leaf1_id = writer.alloc_page_id();
2275            let leaf2_id = writer.alloc_page_id();
2276            let leaf3_id = writer.alloc_page_id();
2277            let leaf4_id = writer.alloc_page_id();
2278            let leaf1 = FreeListLeafNode {
2279                keys: vec![tsn1],
2280                values: vec![FreeListLeafValue {
2281                    page_ids: vec![pid1, pid2],
2282                    root_id: PageID(0),
2283                }],
2284            };
2285            let leaf2 = FreeListLeafNode {
2286                keys: vec![tsn2],
2287                values: vec![FreeListLeafValue {
2288                    page_ids: vec![pid3, pid4],
2289                    root_id: PageID(0),
2290                }],
2291            };
2292            let leaf3 = FreeListLeafNode {
2293                keys: vec![tsn3],
2294                values: vec![FreeListLeafValue {
2295                    page_ids: vec![pid5, pid6],
2296                    root_id: PageID(0),
2297                }],
2298            };
2299            let leaf4 = FreeListLeafNode {
2300                keys: vec![tsn4],
2301                values: vec![FreeListLeafValue {
2302                    page_ids: vec![pid7, pid8],
2303                    root_id: PageID(0),
2304                }],
2305            };
2306            writer
2307                .insert_dirty(Page::new(leaf1_id, Node::FreeListLeaf(leaf1)))
2308                .unwrap();
2309            writer
2310                .insert_dirty(Page::new(leaf2_id, Node::FreeListLeaf(leaf2)))
2311                .unwrap();
2312            writer
2313                .insert_dirty(Page::new(leaf3_id, Node::FreeListLeaf(leaf3)))
2314                .unwrap();
2315            writer
2316                .insert_dirty(Page::new(leaf4_id, Node::FreeListLeaf(leaf4)))
2317                .unwrap();
2318
2319            // Internal root pointing to the two leaves (keys are not used by traversal here)
2320            let internal1 = FreeListInternalNode {
2321                keys: vec![tsn2],
2322                child_ids: vec![leaf1_id, leaf2_id],
2323            };
2324            let internal1_id = writer.alloc_page_id();
2325            let internal2 = FreeListInternalNode {
2326                keys: vec![tsn4],
2327                child_ids: vec![leaf3_id, leaf4_id],
2328            };
2329            let internal2_id = writer.alloc_page_id();
2330            let internal3 = FreeListInternalNode {
2331                keys: vec![tsn3],
2332                child_ids: vec![internal1_id, internal2_id],
2333            };
2334            let internal3_id = writer.alloc_page_id();
2335            writer
2336                .insert_dirty(Page::new(internal1_id, Node::FreeListInternal(internal1)))
2337                .unwrap();
2338            writer
2339                .insert_dirty(Page::new(internal2_id, Node::FreeListInternal(internal2)))
2340                .unwrap();
2341            writer
2342                .insert_dirty(Page::new(internal3_id, Node::FreeListInternal(internal3)))
2343                .unwrap();
2344            writer.append_freed_page_id(writer.free_lists_tree_root_id);
2345            writer.free_lists_tree_root_id = internal3_id;
2346            (
2347                tsn1, tsn2, tsn3, tsn4, pid1, pid2, pid3, pid4, pid5, pid6, pid7, pid8,
2348            )
2349        }
2350
2351        fn build_free_list_tree_leaf_tsn_subtree_leaf(
2352            writer: &mut Writer,
2353        ) -> (PageID, PageID, Tsn) {
2354            // Create a TSN-subtree leaf and make a leaf value point to it
2355            let tsn_sub_leaf_id = writer.alloc_page_id();
2356            let pid1 = writer.alloc_page_id();
2357            let pid2 = writer.alloc_page_id();
2358            let tsn_sub_leaf = crate::free_lists_tree_nodes::FreeListTsnLeafNode {
2359                page_ids: vec![pid1, pid2],
2360            };
2361            writer
2362                .insert_dirty(Page::new(
2363                    tsn_sub_leaf_id,
2364                    Node::FreeListTsnLeaf(tsn_sub_leaf),
2365                ))
2366                .unwrap();
2367
2368            let tsn = Tsn(33);
2369            let leaf = FreeListLeafNode {
2370                keys: vec![tsn],
2371                values: vec![FreeListLeafValue {
2372                    page_ids: vec![],
2373                    root_id: tsn_sub_leaf_id,
2374                }],
2375            };
2376            let root_id = writer.alloc_page_id();
2377            writer
2378                .insert_dirty(Page::new(root_id, Node::FreeListLeaf(leaf)))
2379                .unwrap();
2380            writer.append_freed_page_id(writer.free_lists_tree_root_id);
2381            writer.free_lists_tree_root_id = root_id;
2382            (pid1, pid2, tsn)
2383        }
2384
2385        fn build_free_list_tree_leaf_tsn_subtree_internal_leaf(
2386            writer: &mut Writer,
2387        ) -> (PageID, PageID, PageID, PageID, Tsn) {
2388            // Create a TSN-subtree leaf with a PageID
2389            let tsn_sub_leaf_id1 = writer.alloc_page_id();
2390            let pid1 = writer.alloc_page_id();
2391            let pid2 = writer.alloc_page_id();
2392            let tsn_sub_leaf1 = crate::free_lists_tree_nodes::FreeListTsnLeafNode {
2393                page_ids: vec![pid1, pid2],
2394            };
2395            writer
2396                .insert_dirty(Page::new(
2397                    tsn_sub_leaf_id1,
2398                    Node::FreeListTsnLeaf(tsn_sub_leaf1),
2399                ))
2400                .unwrap();
2401
2402            // Create a TSN-subtree leaf with a PageID
2403            let tsn_sub_leaf_id2 = writer.alloc_page_id();
2404            let pid3 = writer.alloc_page_id();
2405            let pid4 = writer.alloc_page_id();
2406            let tsn_sub_leaf2 = crate::free_lists_tree_nodes::FreeListTsnLeafNode {
2407                page_ids: vec![pid3, pid4],
2408            };
2409            writer
2410                .insert_dirty(Page::new(
2411                    tsn_sub_leaf_id2,
2412                    Node::FreeListTsnLeaf(tsn_sub_leaf2),
2413                ))
2414                .unwrap();
2415
2416            // Create a TSN-subtree internal node
2417            let tsn_sub_internal_id = writer.alloc_page_id();
2418            let tsn_sub_internal = crate::free_lists_tree_nodes::FreeListTsnInternalNode {
2419                keys: vec![pid3],
2420                child_ids: vec![tsn_sub_leaf_id1, tsn_sub_leaf_id2],
2421            };
2422            writer
2423                .insert_dirty(Page::new(
2424                    tsn_sub_internal_id,
2425                    Node::FreeListTsnInternal(tsn_sub_internal),
2426                ))
2427                .unwrap();
2428
2429            // Make a leaf value point to the internal node
2430            let tsn = Tsn(33);
2431            let leaf = FreeListLeafNode {
2432                keys: vec![tsn],
2433                values: vec![FreeListLeafValue {
2434                    page_ids: vec![],
2435                    root_id: tsn_sub_internal_id,
2436                }],
2437            };
2438            let root_id = writer.alloc_page_id();
2439            writer
2440                .insert_dirty(Page::new(root_id, Node::FreeListLeaf(leaf)))
2441                .unwrap();
2442            writer.append_freed_page_id(writer.free_lists_tree_root_id);
2443            writer.free_lists_tree_root_id = root_id;
2444            (pid1, pid2, pid3, pid4, tsn)
2445        }
2446
2447        fn build_free_list_tree_leaf_tsn_subtree_internal_internal_leaf(
2448            writer: &mut Writer,
2449        ) -> (
2450            PageID,
2451            PageID,
2452            PageID,
2453            PageID,
2454            PageID,
2455            PageID,
2456            PageID,
2457            PageID,
2458            Tsn,
2459        ) {
2460            // Create a TSN-subtree leaf with a PageID
2461            let tsn_sub_leaf_id1 = writer.alloc_page_id();
2462            let pid1 = writer.alloc_page_id();
2463            let pid2 = writer.alloc_page_id();
2464            let tsn_sub_leaf1 = crate::free_lists_tree_nodes::FreeListTsnLeafNode {
2465                page_ids: vec![pid1, pid2],
2466            };
2467            writer
2468                .insert_dirty(Page::new(
2469                    tsn_sub_leaf_id1,
2470                    Node::FreeListTsnLeaf(tsn_sub_leaf1),
2471                ))
2472                .unwrap();
2473
2474            // Create a TSN-subtree leaf with a PageID
2475            let tsn_sub_leaf_id2 = writer.alloc_page_id();
2476            let pid3 = writer.alloc_page_id();
2477            let pid4 = writer.alloc_page_id();
2478            let tsn_sub_leaf2 = crate::free_lists_tree_nodes::FreeListTsnLeafNode {
2479                page_ids: vec![pid3, pid4],
2480            };
2481            writer
2482                .insert_dirty(Page::new(
2483                    tsn_sub_leaf_id2,
2484                    Node::FreeListTsnLeaf(tsn_sub_leaf2),
2485                ))
2486                .unwrap();
2487
2488            // Create a TSN-subtree internal node
2489            let tsn_sub_internal_id1 = writer.alloc_page_id();
2490            let tsn_sub_internal1 = crate::free_lists_tree_nodes::FreeListTsnInternalNode {
2491                keys: vec![pid3],
2492                child_ids: vec![tsn_sub_leaf_id1, tsn_sub_leaf_id2],
2493            };
2494            writer
2495                .insert_dirty(Page::new(
2496                    tsn_sub_internal_id1,
2497                    Node::FreeListTsnInternal(tsn_sub_internal1),
2498                ))
2499                .unwrap();
2500
2501            // Create a TSN-subtree leaf with a PageID
2502            let tsn_sub_leaf_id3 = writer.alloc_page_id();
2503            let pid5 = writer.alloc_page_id();
2504            let pid6 = writer.alloc_page_id();
2505            let tsn_sub_leaf3 = crate::free_lists_tree_nodes::FreeListTsnLeafNode {
2506                page_ids: vec![pid5, pid6],
2507            };
2508            writer
2509                .insert_dirty(Page::new(
2510                    tsn_sub_leaf_id3,
2511                    Node::FreeListTsnLeaf(tsn_sub_leaf3),
2512                ))
2513                .unwrap();
2514
2515            // Create a TSN-subtree leaf with a PageID
2516            let tsn_sub_leaf_id4 = writer.alloc_page_id();
2517            let pid7 = writer.alloc_page_id();
2518            let pid8 = writer.alloc_page_id();
2519            let tsn_sub_leaf4 = crate::free_lists_tree_nodes::FreeListTsnLeafNode {
2520                page_ids: vec![pid7, pid8],
2521            };
2522            writer
2523                .insert_dirty(Page::new(
2524                    tsn_sub_leaf_id4,
2525                    Node::FreeListTsnLeaf(tsn_sub_leaf4),
2526                ))
2527                .unwrap();
2528
2529            // Create a TSN-subtree internal node
2530            let tsn_sub_internal_id2 = writer.alloc_page_id();
2531            let tsn_sub_internal2 = crate::free_lists_tree_nodes::FreeListTsnInternalNode {
2532                keys: vec![pid7],
2533                child_ids: vec![tsn_sub_leaf_id3, tsn_sub_leaf_id4],
2534            };
2535            writer
2536                .insert_dirty(Page::new(
2537                    tsn_sub_internal_id2,
2538                    Node::FreeListTsnInternal(tsn_sub_internal2),
2539                ))
2540                .unwrap();
2541
2542            // Create a TSN-subtree internal node
2543            let tsn_sub_internal_id3 = writer.alloc_page_id();
2544            let tsn_sub_internal3 = crate::free_lists_tree_nodes::FreeListTsnInternalNode {
2545                keys: vec![pid5],
2546                child_ids: vec![tsn_sub_internal_id1, tsn_sub_internal_id2],
2547            };
2548            writer
2549                .insert_dirty(Page::new(
2550                    tsn_sub_internal_id3,
2551                    Node::FreeListTsnInternal(tsn_sub_internal3),
2552                ))
2553                .unwrap();
2554
2555            // Make a leaf value point to the internal node
2556            let tsn = writer.tsn;
2557            let leaf = FreeListLeafNode {
2558                keys: vec![tsn],
2559                values: vec![FreeListLeafValue {
2560                    page_ids: vec![],
2561                    root_id: tsn_sub_internal_id3,
2562                }],
2563            };
2564            let root_id = writer.alloc_page_id();
2565            writer
2566                .insert_dirty(Page::new(root_id, Node::FreeListLeaf(leaf)))
2567                .unwrap();
2568            writer.append_freed_page_id(writer.free_lists_tree_root_id);
2569            writer.free_lists_tree_root_id = root_id;
2570            (pid1, pid2, pid3, pid4, pid5, pid6, pid7, pid8, tsn)
2571        }
2572
2573        #[test]
2574        #[serial]
2575        fn test_insert_freed_page_id_to_empty_leaf_root() {
2576            let (_temp_dir, mut db) = construct_mvcc(64);
2577
2578            // Get latest header
2579            let (header_page_id, header_node) = db.get_latest_header().unwrap();
2580            assert_eq!(PageID(0), header_page_id);
2581
2582            // Get next page ID
2583            assert_eq!(PageID(5), header_node.next_page_id);
2584
2585            // Construct a writer
2586            let tsn = Tsn(1001);
2587
2588            let mut writer = Writer::new(
2589                header_page_id,
2590                tsn,
2591                header_node.next_page_id,
2592                header_node.free_lists_tree_root_id,
2593                header_node.events_tree_root_id,
2594                header_node.tags_tree_root_id,
2595                header_node.next_position,
2596                VERBOSE,
2597            );
2598
2599            // Check the free list tree root ID
2600            let initial_root_id = writer.free_lists_tree_root_id;
2601            assert_eq!(PageID(2), initial_root_id);
2602
2603            // Allocate a page ID (to be inserted as "freed")
2604            let page_id = writer.alloc_page_id();
2605
2606            // Check the allocated page ID is the "next" page ID
2607            assert_eq!(header_node.next_page_id, page_id);
2608
2609            // Insert the allocated page ID in the free list tree
2610            let current_tsn = writer.tsn;
2611            writer
2612                .insert_freed_page_id(&mut db, current_tsn, page_id)
2613                .unwrap();
2614
2615            // Check the root page has been CoW-ed
2616            let expected_new_root_id = PageID(header_node.next_page_id.0 + 1);
2617            assert_eq!(expected_new_root_id, writer.free_lists_tree_root_id);
2618            assert_eq!(1, writer.dirty.len());
2619            assert!(writer.dirty.contains_key(&expected_new_root_id));
2620
2621            let new_root_page = writer.dirty.get(&expected_new_root_id).unwrap();
2622            assert_eq!(expected_new_root_id, new_root_page.page_id);
2623
2624            let freed_page_ids: Vec<PageID> = writer.freed_page_ids.iter().cloned().collect();
2625            assert_eq!(vec![initial_root_id], freed_page_ids);
2626
2627            // Check keys and values of the new root page
2628            match &new_root_page.node {
2629                Node::FreeListLeaf(node) => {
2630                    let expected_keys = vec![tsn];
2631                    assert_eq!(expected_keys, node.keys);
2632
2633                    let expected_values = vec![FreeListLeafValue {
2634                        page_ids: vec![header_node.next_page_id],
2635                        root_id: PageID(0),
2636                    }];
2637                    assert_eq!(expected_values, node.values);
2638                }
2639                _ => panic!("Expected FreeListLeaf node"),
2640            }
2641        }
2642
2643        #[test]
2644        #[serial]
2645        fn test_remove_freed_page_id_from_root_leaf_root() {
2646            let (_temp_dir, mut db) = construct_mvcc(64);
2647
2648            // First, insert a page ID
2649            let mut writer;
2650            let inserted_tsn;
2651            let inserted_page_id;
2652            let previous_root_id;
2653
2654            {
2655                // Get a writer
2656                writer = db.writer().unwrap();
2657
2658                // Remember the initial root ID
2659                previous_root_id = writer.free_lists_tree_root_id;
2660
2661                // Allocate a page ID
2662                inserted_page_id = writer.alloc_page_id();
2663
2664                // Insert the page ID
2665                inserted_tsn = writer.tsn;
2666                writer
2667                    .insert_freed_page_id(&mut db, inserted_tsn, inserted_page_id)
2668                    .unwrap();
2669
2670                // Commit the transaction
2671                db.commit(&mut writer).unwrap();
2672            }
2673
2674            // Block inserted page IDs from being reused
2675            {
2676                db.reader_tsns.insert(0, Tsn(0));
2677            }
2678
2679            // Start a new writer to remove inserted freed page ID
2680            {
2681                writer = db.writer().unwrap();
2682
2683                // Check there are no free pages (because we blocked them with a reader)
2684                assert_eq!(0, writer.reusable_page_ids.len());
2685
2686                // Remember the initial root ID and next page ID
2687                let initial_root_id = writer.free_lists_tree_root_id;
2688                let next_page_id = writer.next_page_id;
2689
2690                // Remove what was inserted
2691                writer
2692                    .remove_free_page_id(&db, inserted_tsn, inserted_page_id)
2693                    .unwrap();
2694
2695                // Check the root page has been CoW-ed
2696                let expected_new_root_id = next_page_id;
2697                assert_eq!(expected_new_root_id, writer.free_lists_tree_root_id);
2698                assert_eq!(1, writer.dirty.len());
2699                assert!(writer.dirty.contains_key(&expected_new_root_id));
2700
2701                let new_root_page = writer.dirty.get(&expected_new_root_id).unwrap();
2702                assert_eq!(expected_new_root_id, new_root_page.page_id);
2703
2704                let freed_page_ids: Vec<PageID> = writer.freed_page_ids.iter().cloned().collect();
2705                assert_eq!(vec![initial_root_id], freed_page_ids);
2706
2707                // Check keys and values of the new root page
2708                match &new_root_page.node {
2709                    Node::FreeListLeaf(node) => {
2710                        let expected_keys = vec![inserted_tsn];
2711                        assert_eq!(expected_keys, node.keys);
2712
2713                        let expected_values = vec![FreeListLeafValue {
2714                            page_ids: vec![previous_root_id],
2715                            root_id: PageID(0),
2716                        }];
2717                        assert_eq!(expected_values, node.values);
2718                    }
2719                    _ => panic!("Expected FreeListLeaf node"),
2720                }
2721            }
2722        }
2723
2724        #[test]
2725        #[serial]
2726        fn test_insert_freed_page_ids_until_split_leaf() {
2727            let (_temp_dir, mut db) = construct_mvcc(64);
2728
2729            // Get latest header
2730            let (header_page_id, header_node) = db.get_latest_header().unwrap();
2731
2732            // Create a writer
2733            let mut tsn = Tsn(100);
2734            let mut writer = Writer::new(
2735                header_page_id,
2736                Tsn(header_node.tsn.0 + 1),
2737                header_node.next_page_id,
2738                header_node.free_lists_tree_root_id,
2739                header_node.events_tree_root_id,
2740                header_node.tags_tree_root_id,
2741                header_node.next_position,
2742                VERBOSE,
2743            );
2744
2745            let mut has_split_leaf = false;
2746            let mut inserted: Vec<(Tsn, PageID)> = Vec::new();
2747
2748            // Insert page IDs until we split a leaf
2749            while !has_split_leaf {
2750                // Allocate and insert the first page ID
2751                let page_id1 = writer.alloc_page_id();
2752                writer.insert_freed_page_id(&mut db, tsn, page_id1).unwrap();
2753                inserted.push((tsn, page_id1));
2754
2755                // Allocate and insert second page ID
2756                let page_id2 = writer.alloc_page_id();
2757                writer.insert_freed_page_id(&mut db, tsn, page_id2).unwrap();
2758                inserted.push((tsn, page_id2));
2759
2760                // Increment TSN for next iteration
2761                tsn = Tsn(tsn.0 + 1);
2762
2763                // Check if we've split the leaf
2764                let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
2765                match &root_page.node {
2766                    Node::FreeListInternal(_) => {
2767                        has_split_leaf = true;
2768                    }
2769                    _ => {}
2770                }
2771            }
2772
2773            // Check keys and values of all pages
2774            let mut copy_inserted = inserted.clone();
2775
2776            // Get the root node
2777            let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
2778            let root_node = match &root_page.node {
2779                Node::FreeListInternal(node) => node,
2780                _ => panic!("Expected FreeListInternal node"),
2781            };
2782
2783            // Collect active page IDs
2784            let mut active_page_ids = vec![
2785                HEADER_PAGE_ID_0,
2786                HEADER_PAGE_ID_1,
2787                writer.free_lists_tree_root_id,
2788                writer.events_tree_root_id,
2789                writer.tags_tree_root_id,
2790            ];
2791
2792            // Collect freed page IDs
2793            let mut freed_page_ids: Vec<PageID> = writer.freed_page_ids.iter().cloned().collect();
2794
2795            // Check each child of the root
2796            for (i, &child_id) in root_node.child_ids.iter().enumerate() {
2797                active_page_ids.push(child_id);
2798
2799                let child_page = writer.dirty.get(&child_id).unwrap();
2800                assert_eq!(child_id, child_page.page_id);
2801
2802                let child_node = match &child_page.node {
2803                    Node::FreeListLeaf(node) => node,
2804                    _ => panic!("Expected FreeListLeaf node"),
2805                };
2806
2807                // Check that the keys are properly ordered
2808                if i > 0 {
2809                    assert_eq!(root_node.keys[i - 1], child_node.keys[0]);
2810                }
2811
2812                // Check each key and value in the child
2813                for (k, &key) in child_node.keys.iter().enumerate() {
2814                    for &value in &child_node.values[k].page_ids {
2815                        let (inserted_tsn, inserted_page_id) = copy_inserted.remove(0);
2816                        assert_eq!(inserted_tsn, key);
2817                        assert_eq!(inserted_page_id, value);
2818                        freed_page_ids.push(value);
2819                    }
2820                }
2821            }
2822
2823            // We just split a leaf, so now we have 6 pages
2824            assert_eq!(7, active_page_ids.len());
2825
2826            // Audit page IDs
2827            let mut all_page_ids = active_page_ids.clone();
2828            all_page_ids.extend(freed_page_ids.clone());
2829            all_page_ids.sort();
2830            all_page_ids.dedup();
2831
2832            assert_eq!(
2833                all_page_ids.len(),
2834                active_page_ids.len() + freed_page_ids.len()
2835                    - active_page_ids
2836                        .iter()
2837                        .filter(|id| freed_page_ids.contains(id))
2838                        .count()
2839            );
2840
2841            // Check that all page IDs are accounted for
2842            let expected_page_ids: Vec<PageID> = (0..writer.next_page_id.0).map(PageID).collect();
2843            assert_eq!(expected_page_ids, all_page_ids);
2844        }
2845
2846        #[test]
2847        #[serial]
2848        fn test_insert_freed_page_ids_until_replace_internal_node_child_id() {
2849            let (_temp_dir, db) = construct_mvcc(128);
2850
2851            // Block inserted page IDs from being reused
2852            {
2853                db.reader_tsns.insert(0, Tsn(0));
2854            }
2855
2856            let mut has_split_leaf = false;
2857            let mut inserted: Vec<(Tsn, PageID)> = Vec::new();
2858
2859            // Insert page IDs until we split a leaf
2860            while !has_split_leaf {
2861                // Create a writer
2862                let mut writer = db.writer().unwrap();
2863                // Allocate and insert the first page ID
2864                let page_id1 = writer.alloc_page_id();
2865                writer
2866                    .insert_freed_page_id(&db, writer.tsn, page_id1)
2867                    .unwrap();
2868                inserted.push((writer.tsn, page_id1));
2869
2870                // Check if we've split the leaf
2871                let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
2872                match &root_page.node {
2873                    Node::FreeListInternal(_) => {
2874                        has_split_leaf = true;
2875                    }
2876                    _ => {}
2877                }
2878
2879                if !has_split_leaf {
2880                    // Allocate and insert second page ID
2881                    let page_id2 = writer.alloc_page_id();
2882                    writer
2883                        .insert_freed_page_id(&db, writer.tsn, page_id2)
2884                        .unwrap();
2885                    inserted.push((writer.tsn, page_id2));
2886
2887                    // Check if we've split the leaf
2888                    let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
2889                    match &root_page.node {
2890                        Node::FreeListInternal(_) => {
2891                            has_split_leaf = true;
2892                        }
2893                        _ => {}
2894                    }
2895                }
2896
2897                db.commit(&mut writer).unwrap();
2898            }
2899
2900            let mut writer = db.writer().unwrap();
2901            let page_id3 = writer.alloc_page_id();
2902            writer
2903                .insert_freed_page_id(&db, writer.tsn, page_id3)
2904                .unwrap();
2905            db.commit(&mut writer).unwrap();
2906            inserted.push((writer.tsn, page_id3));
2907
2908            writer = db.writer().unwrap();
2909            let page_id4 = writer.alloc_page_id();
2910            writer
2911                .insert_freed_page_id(&db, writer.tsn, page_id4)
2912                .unwrap();
2913            db.commit(&mut writer).unwrap();
2914            inserted.push((writer.tsn, page_id4));
2915
2916            // Get the root node
2917            writer = db.writer().unwrap();
2918            let root_page = db.read_page(writer.free_lists_tree_root_id).unwrap();
2919            let root_node = match &root_page.node {
2920                Node::FreeListInternal(node) => node,
2921                _ => panic!("Expected FreeListInternal node"),
2922            };
2923
2924            // Collect active page IDs
2925            let mut active_page_ids = vec![
2926                HEADER_PAGE_ID_0,
2927                HEADER_PAGE_ID_1,
2928                writer.free_lists_tree_root_id,
2929                writer.events_tree_root_id,
2930                writer.tags_tree_root_id,
2931            ];
2932
2933            // Collect freed page IDs from the writer
2934            let mut freed_page_ids: Vec<PageID> = writer.freed_page_ids.iter().cloned().collect();
2935
2936            // Collect child page IDs
2937            for (i, &child_id) in root_node.child_ids.iter().enumerate() {
2938                active_page_ids.push(child_id);
2939
2940                // Check each child page
2941                let child_page = db.read_page(child_id).unwrap();
2942                assert_eq!(child_id, child_page.page_id);
2943
2944                let child_node = match &child_page.node {
2945                    Node::FreeListLeaf(node) => node,
2946                    _ => panic!("Expected FreeListLeaf node"),
2947                };
2948
2949                // Check that the keys are properly ordered
2950                if i > 0 {
2951                    assert_eq!(root_node.keys[i - 1], child_node.keys[0]);
2952                }
2953
2954                // Collect freed page IDs from the child page values
2955                for child_value in child_node.values.clone() {
2956                    for page_id in child_value.page_ids {
2957                        freed_page_ids.push(page_id);
2958                    }
2959                }
2960            }
2961
2962            // We have split a leaf node, so now we have 8 active pages
2963            assert_eq!(8, active_page_ids.len());
2964
2965            // Audit page IDs
2966            let mut all_page_ids = active_page_ids.clone();
2967            all_page_ids.extend(freed_page_ids.clone());
2968            all_page_ids.sort();
2969            all_page_ids.dedup();
2970
2971            assert_eq!(
2972                all_page_ids.len(),
2973                active_page_ids.len() + freed_page_ids.len()
2974                    - active_page_ids
2975                        .iter()
2976                        .filter(|id| freed_page_ids.contains(id))
2977                        .count()
2978            );
2979
2980            // Check that all page IDs are accounted for
2981            let expected_page_ids: Vec<PageID> = (0..writer.next_page_id.0).map(PageID).collect();
2982            assert_eq!(expected_page_ids, all_page_ids);
2983        }
2984
2985        #[test]
2986        #[serial]
2987        fn test_remove_freed_page_ids_from_split_leaf() {
2988            let (_temp_dir, mut db) = construct_mvcc(128);
2989
2990            // First, insert page IDs until we split a leaf
2991            if VERBOSE {
2992                println!("Inserting page IDs......");
2993            }
2994            let mut inserted: Vec<(Tsn, PageID)> = Vec::new();
2995            let previous_root_id;
2996            let previous_writer_tsn;
2997
2998            {
2999                // Get a writer
3000                let mut writer = db.writer().unwrap();
3001
3002                // Remember the initial root ID
3003                previous_root_id = writer.free_lists_tree_root_id;
3004
3005                // Insert page IDs until we split a leaf
3006                let mut has_split_leaf = false;
3007                let mut tsn = writer.tsn;
3008
3009                while !has_split_leaf {
3010                    // Increment TSN
3011                    tsn = Tsn(tsn.0 + 1);
3012
3013                    // Allocate and insert the first page ID
3014                    let page_id1 = writer.alloc_page_id();
3015                    writer.insert_freed_page_id(&mut db, tsn, page_id1).unwrap();
3016                    inserted.push((tsn, page_id1));
3017
3018                    // Allocate and insert second page ID
3019                    let page_id2 = writer.alloc_page_id();
3020                    writer.insert_freed_page_id(&mut db, tsn, page_id2).unwrap();
3021                    inserted.push((tsn, page_id2));
3022
3023                    // Check if we've split the leaf
3024                    let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
3025                    match &root_page.node {
3026                        Node::FreeListInternal(_) => {
3027                            has_split_leaf = true;
3028                        }
3029                        _ => {}
3030                    }
3031                }
3032
3033                // Remember the final TSN
3034                writer.tsn = Tsn(tsn.0 + 1);
3035                previous_writer_tsn = writer.tsn;
3036
3037                // Commit the transaction
3038                db.commit(&mut writer).unwrap();
3039            }
3040
3041            // Now remove all the inserted freed page IDs
3042            if VERBOSE {
3043                println!();
3044                println!("Removing all inserted page IDs......");
3045            }
3046
3047            {
3048                // Get latest header
3049                let (header_page_id, header_node) = db.get_latest_header().unwrap();
3050
3051                // Create a new writer
3052                let mut writer = Writer::new(
3053                    header_page_id,
3054                    Tsn(header_node.tsn.0 + 1),
3055                    header_node.next_page_id,
3056                    header_node.free_lists_tree_root_id,
3057                    header_node.events_tree_root_id,
3058                    header_node.tags_tree_root_id,
3059                    header_node.next_position,
3060                    VERBOSE,
3061                );
3062
3063                // Remember the initial root ID
3064                let old_root_id = writer.free_lists_tree_root_id;
3065
3066                // Remove all inserted page IDs
3067                for (tsn, page_id) in inserted.iter() {
3068                    writer.remove_free_page_id(&db, *tsn, *page_id).unwrap();
3069                    if VERBOSE {
3070                        println!("Dirty pages: {:?}", writer.dirty.keys());
3071                    }
3072                }
3073
3074                // Check the root page has been CoW-ed
3075                assert_ne!(old_root_id, writer.free_lists_tree_root_id);
3076
3077                assert_eq!(1, writer.dirty.len());
3078                assert!(writer.dirty.contains_key(&writer.free_lists_tree_root_id));
3079
3080                let new_root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
3081                assert_eq!(writer.free_lists_tree_root_id, new_root_page.page_id);
3082
3083                // Check old root ID is in freed page IDs
3084                let freed_page_ids: Vec<PageID> = writer.freed_page_ids.iter().cloned().collect();
3085                assert!(freed_page_ids.contains(&old_root_id));
3086
3087                // There were three pages, and now we have one. We have
3088                // freed page IDs for three old pages and two CoW pages.
3089                assert_eq!(5, writer.freed_page_ids.len());
3090
3091                // Check keys and values of the new root page
3092                match &new_root_page.node {
3093                    Node::FreeListLeaf(node) => {
3094                        let expected_keys = vec![previous_writer_tsn];
3095                        assert_eq!(expected_keys, node.keys);
3096
3097                        let expected_values = vec![FreeListLeafValue {
3098                            page_ids: vec![previous_root_id],
3099                            root_id: PageID(0),
3100                        }];
3101                        assert_eq!(expected_values, node.values);
3102                    }
3103                    _ => panic!("Expected FreeListLeaf node"),
3104                }
3105
3106                // Audit page IDs
3107                let active_page_ids = vec![
3108                    HEADER_PAGE_ID_0,
3109                    HEADER_PAGE_ID_1,
3110                    writer.free_lists_tree_root_id,
3111                    writer.events_tree_root_id,
3112                    writer.tags_tree_root_id,
3113                ];
3114
3115                // Collect all freed page IDs
3116                let mut all_freed_page_ids = freed_page_ids.clone();
3117
3118                // Add page IDs from the leaf node
3119                match &new_root_page.node {
3120                    Node::FreeListLeaf(node) => {
3121                        for (_, value) in node.keys.iter().zip(node.values.iter()) {
3122                            all_freed_page_ids.extend(value.page_ids.clone());
3123                        }
3124                    }
3125                    _ => panic!("Expected FreeListLeaf node"),
3126                }
3127
3128                // Add inserted page IDs
3129                let inserted_page_ids: Vec<PageID> = inserted.iter().map(|(_, id)| *id).collect();
3130
3131                // Collect all page IDs
3132                let mut all_page_ids = active_page_ids.clone();
3133                all_page_ids.extend(all_freed_page_ids.clone());
3134                all_page_ids.extend(inserted_page_ids.clone());
3135                all_page_ids.sort();
3136                all_page_ids.dedup();
3137
3138                // Check that all page IDs are accounted for
3139                let expected_page_ids: Vec<PageID> =
3140                    (0..writer.next_page_id.0).map(PageID).collect();
3141                assert_eq!(expected_page_ids, all_page_ids);
3142            }
3143        }
3144
3145        #[test]
3146        #[serial]
3147        fn test_insert_freed_page_ids_until_split_internal() {
3148            let (_temp_dir, mut db) = construct_mvcc(64);
3149
3150            // Get latest header
3151            let (header_page_id, header_node) = db.get_latest_header().unwrap();
3152
3153            // Create a writer
3154            let mut writer = Writer::new(
3155                header_page_id,
3156                Tsn(header_node.tsn.0 + 1),
3157                header_node.next_page_id,
3158                header_node.free_lists_tree_root_id,
3159                header_node.events_tree_root_id,
3160                header_node.tags_tree_root_id,
3161                header_node.next_position,
3162                VERBOSE,
3163            );
3164
3165            // Start with TSN 100
3166            let mut tsn = Tsn(100);
3167            let mut inserted: Vec<(Tsn, PageID)> = Vec::new();
3168            let mut has_split_internal = false;
3169
3170            // Insert page IDs until we split an internal node
3171            while !has_split_internal {
3172                // Allocate and insert the first page ID
3173                let page_id1 = writer.alloc_page_id();
3174                writer.insert_freed_page_id(&mut db, tsn, page_id1).unwrap();
3175                inserted.push((tsn, page_id1));
3176
3177                // Allocate and insert second page ID
3178                let page_id2 = writer.alloc_page_id();
3179                writer.insert_freed_page_id(&mut db, tsn, page_id2).unwrap();
3180                inserted.push((tsn, page_id2));
3181
3182                // Increment TSN for next iteration
3183                tsn = Tsn(tsn.0 + 1);
3184
3185                // Check if we've split an internal node
3186                let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
3187                match &root_page.node {
3188                    Node::FreeListInternal(root_node) => {
3189                        // Check if the first child is an internal node
3190                        if !root_node.child_ids.is_empty() {
3191                            let child_id = root_node.child_ids[0];
3192                            if let Some(child_page) = writer.dirty.get(&child_id) {
3193                                match &child_page.node {
3194                                    Node::FreeListInternal(_) => {
3195                                        has_split_internal = true;
3196                                    }
3197                                    _ => {}
3198                                }
3199                            }
3200                        }
3201                    }
3202                    _ => {}
3203                }
3204                if inserted.len() > 100 {
3205                    panic!("Too many inserted page IDs");
3206                }
3207            }
3208
3209            // Check keys and values of all pages
3210            let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
3211            let root_node = match &root_page.node {
3212                Node::FreeListInternal(node) => node,
3213                _ => panic!("Expected FreeListInternal node"),
3214            };
3215
3216            // Collect active page IDs
3217            let mut active_page_ids = vec![
3218                HEADER_PAGE_ID_0,
3219                HEADER_PAGE_ID_1,
3220                writer.free_lists_tree_root_id,
3221                writer.events_tree_root_id,
3222                writer.tags_tree_root_id,
3223            ];
3224
3225            // Collect freed page IDs
3226            let mut freed_page_ids: Vec<PageID> = writer.freed_page_ids.iter().cloned().collect();
3227
3228            // Track the previous child for key ordering checks
3229            let mut previous_child: Option<&FreeListInternalNode> = None;
3230
3231            // Check each child of the root
3232            for (i, &child_id) in root_node.child_ids.iter().enumerate() {
3233                active_page_ids.push(child_id);
3234
3235                let child_page = writer.dirty.get(&child_id).unwrap();
3236                assert_eq!(child_id, child_page.page_id);
3237
3238                let child_node = match &child_page.node {
3239                    Node::FreeListInternal(node) => node,
3240                    _ => panic!("Expected FreeListInternal node"),
3241                };
3242
3243                // Check key ordering between root and child
3244                if i > 0 {
3245                    assert!(root_node.keys[i - 1] < child_node.keys[0]);
3246
3247                    // Check key ordering between previous child and current child
3248                    if let Some(prev_child) = previous_child {
3249                        assert!(root_node.keys[i - 1] > *prev_child.keys.last().unwrap());
3250                    }
3251                }
3252
3253                previous_child = Some(child_node);
3254
3255                // Check each grandchild
3256                for (j, &grand_child_id) in child_node.child_ids.iter().enumerate() {
3257                    active_page_ids.push(grand_child_id);
3258
3259                    let grand_child_page = writer.dirty.get(&grand_child_id).unwrap();
3260                    assert_eq!(grand_child_id, grand_child_page.page_id);
3261
3262                    let grand_child_node = match &grand_child_page.node {
3263                        Node::FreeListLeaf(node) => node,
3264                        _ => panic!("Expected FreeListLeaf node"),
3265                    };
3266
3267                    // Check key ordering between child and grandchild
3268                    if j > 0 {
3269                        assert_eq!(child_node.keys[j - 1], grand_child_node.keys[0]);
3270                    }
3271
3272                    // Check each key and value in the grandchild
3273                    for (k, &key) in grand_child_node.keys.iter().enumerate() {
3274                        for &value in &grand_child_node.values[k].page_ids {
3275                            // Find the matching inserted item
3276                            let pos = inserted.iter().position(|&(t, p)| t == key && p == value);
3277                            if let Some(idx) = pos {
3278                                inserted.remove(idx);
3279                            }
3280                            freed_page_ids.push(value);
3281                        }
3282                    }
3283                }
3284            }
3285
3286            // We should have processed all inserted items
3287            assert!(inserted.is_empty());
3288
3289            // We should have 10 active pages
3290            assert_eq!(11, active_page_ids.len());
3291
3292            // Audit page IDs
3293            let mut all_page_ids = active_page_ids.clone();
3294            all_page_ids.extend(freed_page_ids.clone());
3295            all_page_ids.sort();
3296            all_page_ids.dedup();
3297
3298            // Check that all page IDs are accounted for
3299            let expected_page_ids: Vec<PageID> = (0..writer.next_page_id.0).map(PageID).collect();
3300            assert_eq!(expected_page_ids, all_page_ids);
3301        }
3302
3303        #[test]
3304        #[serial]
3305        fn test_remove_freed_page_ids_from_split_internal() {
3306            let (_temp_dir, mut db) = construct_mvcc(64);
3307
3308            // First, insert page IDs until we split an internal node
3309            let mut inserted: Vec<(Tsn, PageID)> = Vec::new();
3310            let previous_root_id;
3311            let previous_writer_tsn;
3312
3313            {
3314                // Get a writer
3315                let mut writer = db.writer().unwrap();
3316
3317                // Remember the initial root ID
3318                previous_root_id = writer.free_lists_tree_root_id;
3319
3320                // Insert page IDs until we split an internal node
3321                let mut has_split_internal = false;
3322                let mut tsn = writer.tsn;
3323
3324                while !has_split_internal {
3325                    // Increment TSN
3326                    tsn = Tsn(tsn.0 + 1);
3327                    writer.tsn = tsn;
3328
3329                    // Allocate and insert the first page ID
3330                    let page_id1 = writer.alloc_page_id();
3331                    writer.insert_freed_page_id(&mut db, tsn, page_id1).unwrap();
3332                    inserted.push((tsn, page_id1));
3333
3334                    // Allocate and insert second page ID
3335                    let page_id2 = writer.alloc_page_id();
3336                    writer.insert_freed_page_id(&mut db, tsn, page_id2).unwrap();
3337                    inserted.push((tsn, page_id2));
3338
3339                    // Check if we've split an internal node
3340                    let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
3341                    match &root_page.node {
3342                        Node::FreeListInternal(root_node) => {
3343                            // Check if the first child is an internal node
3344                            if !root_node.child_ids.is_empty() {
3345                                let child_id = root_node.child_ids[0];
3346                                if let Some(child_page) = writer.dirty.get(&child_id) {
3347                                    match &child_page.node {
3348                                        Node::FreeListInternal(_) => {
3349                                            has_split_internal = true;
3350                                        }
3351                                        _ => {}
3352                                    }
3353                                }
3354                            }
3355                        }
3356                        _ => {}
3357                    }
3358                }
3359
3360                // Remember the final TSN
3361                previous_writer_tsn = writer.tsn;
3362
3363                // Commit the transaction
3364                db.commit(&mut writer).unwrap();
3365            }
3366
3367            // Now remove all the inserted freed page IDs
3368            {
3369                // Get latest header
3370                let (header_page_id, header_node) = db.get_latest_header().unwrap();
3371
3372                // Create a new writer
3373                let mut writer = Writer::new(
3374                    header_page_id,
3375                    Tsn(header_node.tsn.0 + 1),
3376                    header_node.next_page_id,
3377                    header_node.free_lists_tree_root_id,
3378                    header_node.events_tree_root_id,
3379                    header_node.tags_tree_root_id,
3380                    header_node.next_position,
3381                    VERBOSE,
3382                );
3383
3384                // Remember the initial root ID
3385                let old_root_id = writer.free_lists_tree_root_id;
3386
3387                // Remove all inserted page IDs
3388                for (tsn, page_id) in inserted.iter() {
3389                    writer.remove_free_page_id(&db, *tsn, *page_id).unwrap();
3390                }
3391
3392                // Check the root page has been CoW-ed
3393                assert_ne!(old_root_id, writer.free_lists_tree_root_id);
3394                assert_eq!(1, writer.dirty.len());
3395                assert!(writer.dirty.contains_key(&writer.free_lists_tree_root_id));
3396
3397                let new_root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
3398                assert_eq!(writer.free_lists_tree_root_id, new_root_page.page_id);
3399
3400                // Check old root ID is in freed page IDs
3401                let freed_page_ids: Vec<PageID> = writer.freed_page_ids.iter().cloned().collect();
3402                assert!(freed_page_ids.contains(&old_root_id));
3403
3404                // There were 14 pages, and now we have 1. We have
3405                // freed page IDs for 7 old pages and 6 CoW pages.
3406                assert_eq!(13, writer.freed_page_ids.len());
3407
3408                // Check keys and values of the new root page
3409                match &new_root_page.node {
3410                    Node::FreeListLeaf(node) => {
3411                        let expected_keys = vec![previous_writer_tsn];
3412                        assert_eq!(expected_keys, node.keys);
3413
3414                        let expected_values = vec![FreeListLeafValue {
3415                            page_ids: vec![previous_root_id],
3416                            root_id: PageID(0),
3417                        }];
3418                        assert_eq!(expected_values, node.values);
3419                    }
3420                    _ => panic!("Expected FreeListLeaf node"),
3421                }
3422
3423                // Audit page IDs
3424                let active_page_ids = vec![
3425                    HEADER_PAGE_ID_0,
3426                    HEADER_PAGE_ID_1,
3427                    writer.free_lists_tree_root_id,
3428                    writer.events_tree_root_id,
3429                    writer.tags_tree_root_id,
3430                ];
3431
3432                // Collect all freed page IDs
3433                let mut all_freed_page_ids = freed_page_ids.clone();
3434
3435                // Add page IDs from the leaf node
3436                match &new_root_page.node {
3437                    Node::FreeListLeaf(node) => {
3438                        for (_, value) in node.keys.iter().zip(node.values.iter()) {
3439                            all_freed_page_ids.extend(value.page_ids.clone());
3440                        }
3441                    }
3442                    _ => panic!("Expected FreeListLeaf node"),
3443                }
3444
3445                // Add inserted page IDs
3446                let inserted_page_ids: Vec<PageID> = inserted.iter().map(|(_, id)| *id).collect();
3447
3448                // Collect all page IDs
3449                let mut all_page_ids = active_page_ids.clone();
3450                all_page_ids.extend(all_freed_page_ids.clone());
3451                all_page_ids.extend(inserted_page_ids.clone());
3452                all_page_ids.sort();
3453                all_page_ids.dedup();
3454
3455                // Check that all page IDs are accounted for
3456                let expected_page_ids: Vec<PageID> =
3457                    (0..writer.next_page_id.0).map(PageID).collect();
3458                assert_eq!(expected_page_ids, all_page_ids);
3459            }
3460        }
3461
3462        #[test]
3463        #[serial]
3464        fn test_remove_freed_page_ids_until_replace_old_id_with_orphaned_child_id() {
3465            // This test covers the case where an internal node is removed but leaving a
3466            // child page ID that must be replaced in the parent page, when the parent
3467            // page is not yet dirty. So, here, we must contrive to replace an old ID
3468            // with an orphaned child ID. We can do this by inserting freed page IDs
3469            // until there is an internal page split, and then removing them in order
3470            // until the first internal node is removed, using a new writer each time
3471            // to avoid the internal node becoming dirty before the last key is removed,
3472            // so that its parent internal node (which is the root internal node here)
3473            // will not already have the old child page ID replaced with a dirty
3474            // child page ID.
3475            let (_temp_dir, mut db) = construct_mvcc(128);
3476
3477            // First, insert page IDs until we split an internal node
3478            let mut inserted: Vec<(Tsn, PageID)> = Vec::new();
3479
3480            {
3481                // Get a writer
3482                let mut writer = db.writer().unwrap();
3483
3484                // Insert page IDs until we split an internal node
3485                let mut has_split_internal = false;
3486                let mut tsn = writer.tsn;
3487
3488                while !has_split_internal {
3489                    // Increment TSN
3490                    tsn = Tsn(tsn.0 + 1);
3491                    writer.tsn = tsn;
3492
3493                    // Allocate and insert the first page ID
3494                    let page_id1 = writer.alloc_page_id();
3495                    writer.insert_freed_page_id(&mut db, tsn, page_id1).unwrap();
3496                    inserted.push((tsn, page_id1));
3497
3498                    // Allocate and insert second page ID
3499                    let page_id2 = writer.alloc_page_id();
3500                    writer.insert_freed_page_id(&mut db, tsn, page_id2).unwrap();
3501                    inserted.push((tsn, page_id2));
3502
3503                    // Check if we've split an internal node
3504                    let root_page = writer.dirty.get(&writer.free_lists_tree_root_id).unwrap();
3505                    match &root_page.node {
3506                        Node::FreeListInternal(root_node) => {
3507                            // Check if the first child is an internal node
3508                            if !root_node.child_ids.is_empty() {
3509                                let child_id = root_node.child_ids[0];
3510                                if let Some(child_page) = writer.dirty.get(&child_id) {
3511                                    match &child_page.node {
3512                                        Node::FreeListInternal(_) => {
3513                                            has_split_internal = true;
3514                                        }
3515                                        _ => {}
3516                                    }
3517                                }
3518                            }
3519                        }
3520                        _ => {}
3521                    }
3522                }
3523
3524                // Commit the transaction
3525                db.commit(&mut writer).unwrap();
3526            }
3527
3528            // Get all the free page IDs.
3529            db.reader_tsns.remove(&0);
3530            let writer = db.writer().unwrap();
3531            let reusable_page_ids = writer.reusable_page_ids.clone();
3532
3533            // Block inserted page IDs from being reused
3534            db.reader_tsns.insert(0, Tsn(0));
3535
3536            // Remove each free page ID.
3537            for (page_id, tsn) in reusable_page_ids {
3538                let mut writer = db.writer().unwrap();
3539                writer.remove_free_page_id(&db, tsn, page_id).unwrap();
3540                db.commit(&mut writer).unwrap();
3541            }
3542        }
3543
3544        #[test]
3545        #[serial]
3546        fn test_insert_freed_page_ids_overflow_single_key_moves_to_tsn_subtree() {
3547            // Use tiny pages to force the inline list to overflow quickly
3548            let page_size = 64;
3549            let (_temp_dir, mut mvcc) = construct_mvcc(page_size);
3550
3551            // Start a writer
3552            let mut writer = mvcc.writer().unwrap();
3553            let tsn = writer.tsn;
3554
3555            // With page_size=64, max_node_size = 55. A single key/value with 4 page IDs
3556            // takes 52 bytes, and adding the 5th (+8) would exceed capacity.
3557            let mut inserted_count: usize = 0;
3558            loop {
3559                let pid = writer.alloc_page_id();
3560                writer.insert_freed_page_id(&mut mvcc, tsn, pid).unwrap();
3561                inserted_count += 1;
3562                let dirty_page_id = {
3563                    let mut keys = writer.dirty.keys();
3564                    assert_eq!(keys.len(), 1);
3565                    *keys.next().unwrap()
3566                };
3567                let dirty_page = writer.get_mut_dirty(dirty_page_id).unwrap();
3568                if let Node::FreeListLeaf(leaf_node) = &dirty_page.node {
3569                    if !leaf_node.would_fit_new_page_id(mvcc.max_node_size) {
3570                        break;
3571                    }
3572                } else {
3573                    panic!("Expected leaf node")
3574                }
3575            }
3576
3577            // The next insert for the same TSN should succeed by creating a TSN-subtree
3578            let pid5 = writer.alloc_page_id();
3579            writer.insert_freed_page_id(&mut mvcc, tsn, pid5).unwrap();
3580            inserted_count += 1;
3581
3582            // Insert two more page IDs for the same TSN into the TSN-subtree
3583            let pid6 = writer.alloc_page_id();
3584            writer.insert_freed_page_id(&mut mvcc, tsn, pid6).unwrap();
3585            inserted_count += 1;
3586
3587            let pid7 = writer.alloc_page_id();
3588            writer.insert_freed_page_id(&mut mvcc, tsn, pid7).unwrap();
3589            inserted_count += 1;
3590
3591            // Verify that the leaf now points to a TSN-subtree for this TSN, and that the TSN-subtree root is an internal node (leaf split)
3592            let dirty_ids: Vec<PageID> = { writer.dirty.keys().cloned().collect() };
3593            let mut tsn_root_id = PageID(0);
3594            for dirty_page_id in dirty_ids {
3595                let dirty_page = writer.get_mut_dirty(dirty_page_id).unwrap();
3596                if let Node::FreeListLeaf(leaf_node) = &dirty_page.node {
3597                    assert_eq!(1, leaf_node.keys.len());
3598                    assert_eq!(tsn, leaf_node.keys[0]);
3599                    let val = &leaf_node.values[0];
3600                    assert_eq!(0, val.page_ids.len());
3601                    assert_ne!(PageID(0), val.root_id);
3602                    tsn_root_id = val.root_id;
3603                    break;
3604                }
3605            }
3606            assert_ne!(PageID(0), tsn_root_id);
3607            // The TSN-subtree leaf should have split, so the root must now be internal
3608            // With page_size=64, max_node_size = 55. Seven page IDs exceeds capacity (2 + 8 * 7).
3609            let tsn_root_page = writer.get_page_ref(&mvcc, tsn_root_id).unwrap();
3610            match &tsn_root_page.node {
3611                Node::FreeListTsnInternal(internal) => {
3612                    assert_eq!(2, internal.child_ids.len());
3613                    assert_eq!(1, internal.keys.len());
3614                }
3615                other => panic!(
3616                    "Expected TSN-subtree internal node, got {:?}",
3617                    other.type_name()
3618                ),
3619            }
3620
3621            // Continue inserting page IDs for the same TSN until the TSN-subtree internal node splits
3622            let mut extra_inserts = 0usize;
3623            let mut guard = 0usize;
3624            loop {
3625                guard += 1;
3626                assert!(
3627                    guard < 200,
3628                    "guard hit while waiting for TSN-subtree internal split"
3629                );
3630                let pid = writer.alloc_page_id();
3631                writer.insert_freed_page_id(&mut mvcc, tsn, pid).unwrap();
3632                extra_inserts += 1;
3633
3634                // Refresh tsn_root_id from the FreeList leaf since the root may change when it splits
3635                let dirty_ids: Vec<PageID> = writer.dirty.keys().cloned().collect();
3636                tsn_root_id = PageID(0);
3637                for dirty_page_id in dirty_ids {
3638                    let dirty_page = writer.get_mut_dirty(dirty_page_id).unwrap();
3639                    if let Node::FreeListLeaf(leaf_node) = &dirty_page.node {
3640                        assert_eq!(1, leaf_node.keys.len());
3641                        assert_eq!(tsn, leaf_node.keys[0]);
3642                        let val = &leaf_node.values[0];
3643                        assert_eq!(0, val.page_ids.len());
3644                        assert_ne!(PageID(0), val.root_id);
3645                        tsn_root_id = val.root_id;
3646                        break;
3647                    }
3648                }
3649                assert_ne!(PageID(0), tsn_root_id);
3650
3651                let root_node_owned = {
3652                    writer
3653                        .get_page_ref(&mvcc, tsn_root_id)
3654                        .unwrap()
3655                        .node
3656                        .clone()
3657                };
3658                match root_node_owned {
3659                    Node::FreeListTsnInternal(internal_root) => {
3660                        // If the first child is also an internal node, then the previous internal split promoted a new root
3661                        let first_child_id = internal_root.child_ids[0];
3662                        let first_child_node = {
3663                            writer
3664                                .get_page_ref(&mvcc, first_child_id)
3665                                .unwrap()
3666                                .node
3667                                .clone()
3668                        };
3669                        if matches!(first_child_node, Node::FreeListTsnInternal(_)) {
3670                            // We have achieved an internal split in the TSN-subtree
3671                            // The new root should have exactly 1 key and 2 children after promotion
3672                            assert_eq!(1, internal_root.keys.len());
3673                            assert_eq!(2, internal_root.child_ids.len());
3674                            break;
3675                        }
3676                    }
3677                    other => panic!(
3678                        "Expected TSN-subtree internal node, got {:?}",
3679                        other.type_name()
3680                    ),
3681                }
3682            }
3683
3684            // Now add another PageID to the internal->internal->leaf.
3685            let pid = writer.alloc_page_id();
3686            writer.insert_freed_page_id(&mut mvcc, tsn, pid).unwrap();
3687            extra_inserts += 1;
3688
3689            // Verify all page IDs are discoverable via find_reusable_page_ids
3690            writer.find_reusable_page_ids(&mvcc).unwrap();
3691            assert_eq!(
3692                inserted_count + extra_inserts,
3693                writer.reusable_page_ids.len()
3694            );
3695            for &(_pid, _tsn) in writer.reusable_page_ids.iter() {
3696                assert_eq!(tsn, _tsn);
3697            }
3698        }
3699
3700        #[test]
3701        #[serial]
3702        fn test_remove_freed_page_id_from_tsn_subtree_leaf_pid1() {
3703            let (_temp_dir, db) = construct_mvcc(128);
3704            let mut writer = db.writer().unwrap();
3705
3706            let (pid1, pid2, tsn1) = build_free_list_tree_leaf_tsn_subtree_leaf(&mut writer);
3707
3708            writer.find_reusable_page_ids(&db).unwrap();
3709            assert_eq!(2, writer.reusable_page_ids.len());
3710            assert_eq!((pid1, tsn1), writer.reusable_page_ids[0]);
3711            assert_eq!((pid2, tsn1), writer.reusable_page_ids[1]);
3712
3713            writer.remove_free_page_id(&db, tsn1, pid1).unwrap();
3714            writer.find_reusable_page_ids(&db).unwrap();
3715            assert_eq!(1, writer.reusable_page_ids.len());
3716            assert_eq!((pid2, tsn1), writer.reusable_page_ids[0]);
3717        }
3718
3719        #[test]
3720        #[serial]
3721        fn test_remove_freed_page_id_from_tsn_subtree_leaf_pid2() {
3722            let (_temp_dir, db) = construct_mvcc(128);
3723            let mut writer = db.writer().unwrap();
3724
3725            let (pid1, pid2, tsn1) = build_free_list_tree_leaf_tsn_subtree_leaf(&mut writer);
3726
3727            writer.remove_free_page_id(&db, tsn1, pid2).unwrap();
3728            writer.find_reusable_page_ids(&db).unwrap();
3729            assert_eq!(1, writer.reusable_page_ids.len());
3730            assert_eq!((pid1, tsn1), writer.reusable_page_ids[0]);
3731        }
3732
3733        #[test]
3734        #[serial]
3735        fn test_tsn_subtree_random_order_insert_and_ordering() {
3736            // Moderate page size to force multiple leaves and internals
3737            let page_size = 256;
3738            let (_temp_dir, mut db) = construct_mvcc(page_size);
3739            let mut writer = db.writer().unwrap();
3740            let tsn = writer.tsn;
3741
3742            // Generate many distinct PageIDs, then shuffle their insertion order
3743            let n = 100usize;
3744            let mut pids: Vec<PageID> = Vec::with_capacity(n);
3745            for _ in 0..n {
3746                pids.push(writer.alloc_page_id());
3747            }
3748            // Shuffle by swapping using a simple deterministic pattern to avoid extra deps
3749            for i in 0..pids.len() {
3750                let j = (i * 37 + 13) % pids.len();
3751                pids.swap(i, j);
3752            }
3753            // Insert them all for the same TSN in random order
3754            for pid in &pids {
3755                writer.insert_freed_page_id(&mut db, tsn, *pid).unwrap();
3756            }
3757            // Insert a duplicate of the first ID and ensure it is ignored
3758            let dup = pids[0];
3759            writer.insert_freed_page_id(&mut db, tsn, dup).unwrap();
3760
3761            // Verify via reusable_page_ids
3762            writer.find_reusable_page_ids(&db).unwrap();
3763            assert_eq!(n, writer.reusable_page_ids.len());
3764            for &(_pid, _tsn) in writer.reusable_page_ids.iter() {
3765                assert_eq!(tsn, _tsn);
3766            }
3767
3768            // Inspect the TSN-subtree shape: root should be internal for this many ids
3769            // Find the freelist leaf that holds this TSN and get its root_id
3770            let mut tsn_root_id = PageID(0);
3771            for page_id in writer.dirty.keys().cloned().collect::<Vec<_>>() {
3772                let page = writer.get_mut_dirty(page_id).unwrap();
3773                if let Node::FreeListLeaf(leaf_node) = &page.node {
3774                    if !leaf_node.keys.is_empty() && leaf_node.keys[0] == tsn {
3775                        tsn_root_id = leaf_node.values[0].root_id;
3776                        break;
3777                    }
3778                }
3779            }
3780            assert_ne!(PageID(0), tsn_root_id);
3781            let root_node_owned = { writer.get_page_ref(&db, tsn_root_id).unwrap().node.clone() };
3782            match root_node_owned {
3783                Node::FreeListTsnInternal(internal_root) => {
3784                    // It’s likely that at least one child is internal for n=100 at this page size
3785                    let first_child_id = internal_root.child_ids[0];
3786                    let first_child_node = {
3787                        writer
3788                            .get_page_ref(&db, first_child_id)
3789                            .unwrap()
3790                            .node
3791                            .clone()
3792                    };
3793                    if let Node::FreeListTsnInternal(_in2) = first_child_node {
3794                        // ok, internal->internal exists
3795                    }
3796                }
3797                Node::FreeListTsnLeaf(_) => {
3798                    // With small n this could still be a leaf root; accept but ensure ordering via reusable_page_ids
3799                }
3800                other => panic!("Unexpected node type: {}", other.type_name()),
3801            }
3802        }
3803
3804        #[test]
3805        #[serial]
3806        fn test_upgrade_inline_to_tsn_subtree_with_random_inserts_and_duplicates() {
3807            // Small page size to quickly overflow inline and create a TSN-subtree
3808            let page_size = 96;
3809            let (_temp_dir, mut mvcc) = construct_mvcc(page_size);
3810            let mut writer = mvcc.writer().unwrap();
3811            let tsn = writer.tsn;
3812
3813            // Insert a few to fill inline list close to capacity
3814            let mut inline_ids = Vec::new();
3815            loop {
3816                let pid = writer.alloc_page_id();
3817                let res = writer.insert_freed_page_id(&mut mvcc, tsn, pid);
3818                if res.is_err() {
3819                    panic!("unexpected error inserting into inline");
3820                }
3821                inline_ids.push(pid);
3822                // Heuristic to break when the next inline would likely overflow and cause move-to-subtree
3823                let dirty_id = writer.dirty.keys().cloned().next().unwrap();
3824                if let Node::FreeListLeaf(leaf_node) = &writer.get_mut_dirty(dirty_id).unwrap().node
3825                {
3826                    if !leaf_node.would_fit_new_page_id(mvcc.max_node_size) {
3827                        break;
3828                    }
3829                }
3830            }
3831
3832            // Now insert a batch of random ids including some duplicates
3833            let mut extra_ids = Vec::new();
3834            for _ in 0..30 {
3835                extra_ids.push(writer.alloc_page_id());
3836            }
3837            // Shuffle simple
3838            for i in 0..extra_ids.len() {
3839                let j = (i * 29 + 7) % extra_ids.len();
3840                extra_ids.swap(i, j);
3841            }
3842            // Add some duplicates from earlier inline ids
3843            if !inline_ids.is_empty() {
3844                extra_ids.push(inline_ids[0]);
3845            }
3846            if inline_ids.len() > 1 {
3847                extra_ids.push(inline_ids[1]);
3848            }
3849
3850            for pid in &extra_ids {
3851                writer.insert_freed_page_id(&mut mvcc, tsn, *pid).unwrap();
3852            }
3853
3854            // Verify no duplicates and all ids present
3855            writer.find_reusable_page_ids(&mvcc).unwrap();
3856            let mut expected: Vec<PageID> = inline_ids.clone();
3857            for p in extra_ids {
3858                if !expected.contains(&p) {
3859                    expected.push(p);
3860                }
3861            }
3862            expected.sort_by_key(|p| p.0);
3863            expected.dedup();
3864            let mut actual: Vec<PageID> =
3865                writer.reusable_page_ids.iter().map(|(p, _)| *p).collect();
3866            actual.sort_by_key(|p| p.0);
3867            assert_eq!(expected, actual);
3868        }
3869
3870        #[test]
3871        #[serial]
3872        fn test_remove_freed_page_id_from_tsn_subtree_leaf_all() {
3873            let (_temp_dir, db) = construct_mvcc(128);
3874            let mut writer = db.writer().unwrap();
3875
3876            let (pid1, pid2, tsn1) = build_free_list_tree_leaf_tsn_subtree_leaf(&mut writer);
3877
3878            writer.remove_free_page_id(&db, tsn1, pid1).unwrap();
3879            writer.remove_free_page_id(&db, tsn1, pid2).unwrap();
3880            writer.find_reusable_page_ids(&db).unwrap();
3881            assert_eq!(0, writer.reusable_page_ids.len());
3882
3883            assert_eq!(2, writer.freed_page_ids.len());
3884        }
3885
3886        #[test]
3887        #[serial]
3888        fn test_remove_freed_page_id_from_tsn_subtree_internal_leaf_pid1() {
3889            let (_temp_dir, db) = construct_mvcc(128);
3890            let mut writer = db.writer().unwrap();
3891
3892            let (pid1, pid2, pid3, pid4, tsn1) =
3893                build_free_list_tree_leaf_tsn_subtree_internal_leaf(&mut writer);
3894
3895            writer.remove_free_page_id(&db, tsn1, pid1).unwrap();
3896            writer.find_reusable_page_ids(&db).unwrap();
3897            assert_eq!(3, writer.reusable_page_ids.len());
3898            assert_eq!((pid2, tsn1), writer.reusable_page_ids[0]);
3899            assert_eq!((pid3, tsn1), writer.reusable_page_ids[1]);
3900            assert_eq!((pid4, tsn1), writer.reusable_page_ids[2]);
3901
3902            assert_eq!(1, writer.freed_page_ids.len());
3903
3904            writer.remove_free_page_id(&db, tsn1, pid2).unwrap();
3905            writer.find_reusable_page_ids(&db).unwrap();
3906            assert_eq!(2, writer.reusable_page_ids.len());
3907            assert_eq!((pid3, tsn1), writer.reusable_page_ids[0]);
3908            assert_eq!((pid4, tsn1), writer.reusable_page_ids[1]);
3909
3910            assert_eq!(3, writer.freed_page_ids.len());
3911
3912            writer.remove_free_page_id(&db, tsn1, pid3).unwrap();
3913            writer.find_reusable_page_ids(&db).unwrap();
3914            assert_eq!(1, writer.reusable_page_ids.len());
3915            assert_eq!((pid4, tsn1), writer.reusable_page_ids[0]);
3916
3917            assert_eq!(3, writer.freed_page_ids.len());
3918
3919            writer.remove_free_page_id(&db, tsn1, pid4).unwrap();
3920            writer.find_reusable_page_ids(&db).unwrap();
3921            assert_eq!(0, writer.reusable_page_ids.len());
3922
3923            assert_eq!(4, writer.freed_page_ids.len());
3924        }
3925
3926        #[test]
3927        #[serial]
3928        fn test_remove_freed_page_id_from_tsn_subtree_internal_internal_leaf_pid1() {
3929            let (_temp_dir, db) = construct_mvcc(128);
3930            let mut writer = db.writer().unwrap();
3931
3932            let (pid1, pid2, pid3, pid4, pid5, pid6, pid7, pid8, tsn) =
3933                build_free_list_tree_leaf_tsn_subtree_internal_internal_leaf(&mut writer);
3934
3935            writer.remove_free_page_id(&db, tsn, pid1).unwrap();
3936            writer.find_reusable_page_ids(&db).unwrap();
3937            assert_eq!(7, writer.reusable_page_ids.len());
3938            assert_eq!((pid2, tsn), writer.reusable_page_ids[0]);
3939            assert_eq!((pid3, tsn), writer.reusable_page_ids[1]);
3940            assert_eq!((pid4, tsn), writer.reusable_page_ids[2]);
3941            assert_eq!((pid5, tsn), writer.reusable_page_ids[3]);
3942            assert_eq!((pid6, tsn), writer.reusable_page_ids[4]);
3943            assert_eq!((pid7, tsn), writer.reusable_page_ids[5]);
3944            assert_eq!((pid8, tsn), writer.reusable_page_ids[6]);
3945
3946            assert_eq!(1, writer.freed_page_ids.len());
3947        }
3948
3949        #[test]
3950        #[serial]
3951        fn test_remove_freed_page_id_cow_does_not_leak_ids_in_tsn_subtree() {
3952            // Build a TSN-subtree with depth (internal->internal->leaf), commit it, then
3953            // perform a removal in a new writer to force copy-on-write on the subtree path.
3954            let (_temp_dir, db) = construct_mvcc(128);
3955
3956            // First writer builds the TSN-subtree and commits
3957            let w1_tsn = {
3958                let mut w1 = db.writer().unwrap();
3959                let (_pid1, _pid2, _pid3, _pid4, _pid5, _pid6, _pid7, _pid8, tsn) =
3960                    build_free_list_tree_leaf_tsn_subtree_internal_internal_leaf(&mut w1);
3961                // Commit so that subsequent modifications must use COW
3962                db.commit(&mut w1).unwrap();
3963                // Keep tsn for next phase via storing in header
3964                tsn
3965            };
3966
3967            // Second writer removes a page id to trigger COW on nodes along the path
3968            let mut w2 = db.writer().unwrap();
3969            // Find one reusable pid to remove via API, ensure tree populated
3970            w2.find_reusable_page_ids(&db).unwrap();
3971            assert!(w2.reusable_page_ids.len() >= 1);
3972            let (remove_pid, remove_tsn) = w2.reusable_page_ids[0];
3973            assert_eq!(remove_tsn, w1_tsn); // All built under this tsn in the helper
3974            w2.remove_free_page_id(&db, remove_tsn, remove_pid).unwrap();
3975
3976            // Now audit: every page id from 0..next_page_id must be either active or freed.
3977            // Collect active page ids by traversing the current free list tree, including TSN-subtrees.
3978            let mut active: Vec<PageID> = Vec::new();
3979            let mut freed_tree: Vec<PageID> = Vec::new();
3980            // Always-active headers and roots
3981            active.push(HEADER_PAGE_ID_0);
3982            active.push(HEADER_PAGE_ID_1);
3983            active.push(w2.free_lists_tree_root_id);
3984            active.push(w2.events_tree_root_id);
3985            active.push(w2.tags_tree_root_id);
3986
3987            // Traverse free list tree from current root, reading nodes from disk or cache
3988            let mut stack: Vec<PageID> = vec![w2.free_lists_tree_root_id];
3989            while let Some(pid) = stack.pop() {
3990                if active.contains(&pid) == false {
3991                    active.push(pid);
3992                }
3993                let node_owned = { w2.get_page_ref(&db, pid).unwrap().node.clone() };
3994                match node_owned {
3995                    Node::FreeListInternal(internal) => {
3996                        for child in internal.child_ids {
3997                            stack.push(child);
3998                        }
3999                    }
4000                    Node::FreeListLeaf(leaf) => {
4001                        // Any inline free page IDs are considered freed
4002                        for val in &leaf.values {
4003                            for &p in &val.page_ids {
4004                                freed_tree.push(p);
4005                            }
4006                        }
4007                        // If TSN-subtree root exists, traverse it too
4008                        for val in leaf.values {
4009                            if val.root_id != PageID(0) {
4010                                let mut tsn_stack: Vec<PageID> = vec![val.root_id];
4011                                while let Some(tid) = tsn_stack.pop() {
4012                                    if active.contains(&tid) == false {
4013                                        active.push(tid);
4014                                    }
4015                                    let tnode = { w2.get_page_ref(&db, tid).unwrap().node.clone() };
4016                                    match tnode {
4017                                        Node::FreeListTsnInternal(tint) => {
4018                                            for c in tint.child_ids {
4019                                                tsn_stack.push(c);
4020                                            }
4021                                        }
4022                                        Node::FreeListTsnLeaf(tleaf) => {
4023                                            for &p in &tleaf.page_ids {
4024                                                freed_tree.push(p);
4025                                            }
4026                                        }
4027                                        _ => panic!("Unexpected node type in TSN-subtree"),
4028                                    }
4029                                }
4030                            }
4031                        }
4032                    }
4033                    _ => panic!("Unexpected node type in free list tree"),
4034                }
4035            }
4036
4037            // Also include any currently-dirty pages (new COW copies) as active
4038            for (&pid, _page) in w2.dirty.iter() {
4039                if !active.contains(&pid) {
4040                    active.push(pid);
4041                }
4042            }
4043            // And include any pages we've read/deserialized during traversal
4044            for (&pid, _page) in w2.deserialized.iter() {
4045                if !active.contains(&pid) {
4046                    active.push(pid);
4047                }
4048            }
4049
4050            // Collect freed ids recorded by writer due to COW or structural removals
4051            let mut freed: Vec<PageID> = w2.freed_page_ids.iter().cloned().collect();
4052            // Include the reusable ids present in the tree itself
4053            freed.extend(freed_tree);
4054            // Include the reusable ids tracked by the writer snapshot
4055            for (pid, _tsn) in w2.reusable_page_ids.iter() {
4056                freed.push(*pid);
4057            }
4058
4059            // Validate coverage
4060            active.sort_by_key(|p| p.0);
4061            active.dedup();
4062            freed.sort_by_key(|p| p.0);
4063            freed.dedup();
4064            let mut union = active.clone();
4065            for id in &freed {
4066                if !union.contains(id) {
4067                    union.push(*id);
4068                }
4069            }
4070            union.sort_by_key(|p| p.0);
4071
4072            let expected: Vec<PageID> = (0..w2.next_page_id.0).map(PageID).collect();
4073            if expected != union {
4074                let mut missing: Vec<PageID> = expected
4075                    .iter()
4076                    .cloned()
4077                    .filter(|p| !union.contains(p))
4078                    .collect();
4079                let mut unexpected: Vec<PageID> = union
4080                    .iter()
4081                    .cloned()
4082                    .filter(|p| !expected.contains(p))
4083                    .collect();
4084                missing.sort_by_key(|p| p.0);
4085                unexpected.sort_by_key(|p| p.0);
4086                panic!(
4087                    "Missing: {:?} Unexpected: {:?}\nactive: {:?}\nfreed: {:?}",
4088                    missing, unexpected, active, freed
4089                );
4090            }
4091            assert_eq!(
4092                expected, union,
4093                "All page IDs must be accounted for (active or freed)"
4094            );
4095        }
4096    }
4097}