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