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