Skip to main content

umadb_core/
mvcc.rs

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