Skip to main content

umadb_core/
mvcc.rs

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