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