1use super::freelist::FreeList;
9use super::page::{Page, PageError, PageType, PAGE_SIZE};
10use super::page_cache::PageCache;
11use super::pager_io::{self, FIRST_ALLOCATABLE_PAGE};
12use reddb_file::VfsFile;
13pub use reddb_file::{DatabaseHeader, PhysicalFileHeader};
14use std::path::{Path, PathBuf};
15use std::sync::{Mutex, RwLock};
16
17#[derive(Debug)]
19pub enum PagerError {
20 Io(std::io::Error),
22 Page(PageError),
24 InvalidDatabase(String),
26 ReadOnly,
28 PageNotFound(u32),
30 Locked,
32 LockPoisoned,
34 EncryptionRequired,
36 PlainDatabaseRefusesKey,
38 InvalidKey,
40 SuperblockZoneUnrecoverable(PathBuf),
42 ManifestZoneCorrupt(PathBuf),
44 DwbZoneCorrupt(PathBuf),
46 LegacySidecarStore { sidecar: PathBuf },
48 LegacyPagedStore { path: PathBuf },
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct ExtentId {
55 pub start_page: u32,
56 pub n_pages: u32,
57}
58
59impl std::fmt::Display for PagerError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 Self::Io(e) => write!(f, "I/O error: {}", e),
63 Self::Page(e) => write!(f, "Page error: {}", e),
64 Self::InvalidDatabase(msg) => write!(f, "Invalid database: {}", msg),
65 Self::ReadOnly => write!(f, "Database is read-only"),
66 Self::PageNotFound(id) => write!(f, "Page {} not found", id),
67 Self::Locked => write!(f, "Database is locked"),
68 Self::LockPoisoned => write!(f, "Internal lock poisoned (concurrent thread panicked)"),
69 Self::EncryptionRequired => write!(
70 f,
71 "Database is encrypted but no key was supplied (set PagerConfig::encryption)"
72 ),
73 Self::PlainDatabaseRefusesKey => write!(
74 f,
75 "Plain (unencrypted) database opened with an encryption key — refusing"
76 ),
77 Self::InvalidKey => write!(f, "Encryption key validation failed for this database"),
78 Self::SuperblockZoneUnrecoverable(path) => write!(
79 f,
80 "superblock zone of {} is unrecoverable: both ping-pong copies failed \
81 validation, so no generation can be trusted to root the store. The store \
82 will not be opened. Recover what survives with red salvage \
83 (ADR 0074 §4); it reads the damaged file without writing to it and \
84 reports what it could not verify.",
85 path.display()
86 ),
87 Self::ManifestZoneCorrupt(path) => write!(
88 f,
89 "internal manifest zone of {} failed its checksum: the zone that names \
90 collections, indexes and the checkpoint boundary cannot be trusted, so no \
91 rows are returned rather than garbage ones. Run the `SCRUB` statement to classify the fault \
92 and `red salvage` to extract what survives (ADR 0074 §2/§4).",
93 path.display()
94 ),
95 Self::DwbZoneCorrupt(path) => write!(
96 f,
97 "double-write zone of {} failed validation: page recovery cannot prove \
98 whether the protected in-place write completed. The store will not be opened \
99 silently; run the `SCRUB` statement to classify the fault and `red salvage` to extract trusted \
100 rows (ADR 0074 §2/§4).",
101 path.display()
102 ),
103 Self::LegacySidecarStore { sidecar } => write!(
104 f,
105 "refusing to open a legacy sidecar-backed store: found {}. The superblock \
106 pair, internal manifest, and double-write recovery state now live inside the \
107 .rdb file (ADR 0038 §2/§4), \
108 and the engine never reads the retired sidecars silently. Convert the store \
109 first with the offline migration tool \
110 (`reddb_server::pager_zone_migration::migrate_to_zoned`); it is reversible.",
111 sidecar.display()
112 ),
113 Self::LegacyPagedStore { path } => write!(
114 f,
115 "refusing to open a pre-phase-3 paged store: {} does not carry the layout marker \
116 for the in-file double-write zone. Convert the store first with the offline \
117 migration tool (`reddb_server::pager_zone_migration::migrate_to_zoned`); it is \
118 reversible.",
119 path.display()
120 ),
121 }
122 }
123}
124
125impl std::error::Error for PagerError {
126 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
127 match self {
128 Self::Io(e) => Some(e),
129 Self::Page(e) => Some(e),
130 _ => None,
131 }
132 }
133}
134
135impl From<std::io::Error> for PagerError {
136 fn from(e: std::io::Error) -> Self {
137 Self::Io(e)
138 }
139}
140
141impl From<PageError> for PagerError {
142 fn from(e: PageError) -> Self {
143 Self::Page(e)
144 }
145}
146
147impl From<pager_io::PagerIoError> for PagerError {
148 fn from(error: pager_io::PagerIoError) -> Self {
149 match error {
150 pager_io::PagerIoError::Io(error) => Self::Io(error),
151 pager_io::PagerIoError::Page(error) => Self::Page(error),
152 pager_io::PagerIoError::InvalidDatabase(message) => Self::InvalidDatabase(message),
153 }
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct PagerConfig {
160 pub cache_size: usize,
161 pub read_only: bool,
162 pub verify_checksums: bool,
163 pub double_write: bool,
164}
165
166impl Default for PagerConfig {
167 fn default() -> Self {
168 Self {
169 cache_size: 10_000,
170 read_only: false,
171 verify_checksums: true,
172 double_write: true,
173 }
174 }
175}
176
177pub trait WalBarrier {
180 fn flush_until(&self, lsn: u64) -> std::io::Result<()>;
181}
182
183pub struct NoWal;
185impl WalBarrier for NoWal {
186 fn flush_until(&self, _lsn: u64) -> std::io::Result<()> {
187 Err(std::io::Error::other(
188 "nonzero page LSN requires a WAL durability barrier",
189 ))
190 }
191}
192pub struct Pager<F: VfsFile, W: WalBarrier> {
193 path: PathBuf,
195 file: Mutex<F>,
197 dwb_enabled: bool,
199 cache: PageCache,
201 freelist: RwLock<FreeList>,
203 deferred_reclamation: Mutex<DeferredPageReclamation>,
206 allocation_reused: std::sync::atomic::AtomicBool,
209 allocation_fence_pending: std::sync::atomic::AtomicBool,
211 header: RwLock<DatabaseHeader>,
213 config: PagerConfig,
215 header_dirty: Mutex<bool>,
217 wal: W,
218}
219
220#[derive(Default)]
221struct DeferredPageReclamation {
222 enabled: bool,
223 pending: std::collections::BTreeSet<u32>,
224 published: std::collections::BTreeSet<u32>,
225}
226
227const SUPERBLOCK_SLOT_SIZE: usize = reddb_file::PAGED_SUPERBLOCK_SLOT_SIZE;
229const SUPERBLOCK_ZONE_SIZE: usize = reddb_file::PAGED_SUPERBLOCK_ZONE_SIZE;
230const PHASE3_DWB_ZONE_FILE_VERSION: u32 = reddb_file::PAGE_FILE_VERSION;
231
232pub type NewestSuperblock = (
234 reddb_file::PagedSuperblockSelection,
235 [u8; SUPERBLOCK_SLOT_SIZE],
236);
237
238fn superblock_slot_image(page: &[u8; PAGE_SIZE]) -> [u8; SUPERBLOCK_SLOT_SIZE] {
241 let mut image = [0u8; SUPERBLOCK_SLOT_SIZE];
242 image.copy_from_slice(&page[..SUPERBLOCK_SLOT_SIZE]);
243 image
244}
245
246impl<F: VfsFile, W: WalBarrier> Pager<F, W> {
247 pub fn open_owned(
251 file: F,
252 path: PathBuf,
253 config: PagerConfig,
254 wal: W,
255 create: bool,
256 ) -> Result<Self, PagerError> {
257 if create && file.file_len()? != 0 {
258 return Err(PagerError::InvalidDatabase(
259 "new pager file is not empty".into(),
260 ));
261 }
262 let pager = Self {
263 path,
264 file: Mutex::new(file),
265 dwb_enabled: config.double_write && !config.read_only,
266 cache: PageCache::new(config.cache_size),
267 freelist: RwLock::new(FreeList::new()),
268 deferred_reclamation: Mutex::new(DeferredPageReclamation::default()),
269 allocation_reused: std::sync::atomic::AtomicBool::new(false),
270 allocation_fence_pending: std::sync::atomic::AtomicBool::new(false),
271 header: RwLock::new(DatabaseHeader::default()),
272 config,
273 header_dirty: Mutex::new(false),
274 wal,
275 };
276 if create {
277 pager.initialize()?;
278 } else {
279 pager.ensure_phase3_dwb_zone_layout()?;
280 pager.recover_from_dwb()?;
281 pager.load_header()?;
282 }
283 Ok(pager)
284 }
285
286 pub fn wal_barrier(&self) -> &W {
287 &self.wal
288 }
289
290 #[doc(hidden)]
293 pub fn with_file<T>(&self, operation: impl FnOnce(&mut F) -> T) -> Result<T, PagerError> {
294 Ok(operation(&mut *self.file_lock()?))
295 }
296
297 fn initialize(&self) -> Result<(), PagerError> {
299 if self.config.read_only {
300 return Err(PagerError::ReadOnly);
301 }
302
303 let initial_page_count = FIRST_ALLOCATABLE_PAGE;
307 let header_page = Page::new_header_page(initial_page_count);
308 self.header_write()?.page_count = initial_page_count;
309
310 self.write_page_raw(0, &header_page)?;
315 let mut metadata_page = Page::new(PageType::Header, 1);
316 metadata_page.update_checksum();
317 self.write_page_raw(1, &metadata_page)?;
318 let mut vault_page = Page::new(PageType::Vault, 2);
319 vault_page.update_checksum();
320 self.write_page_raw(2, &vault_page)?;
321 self.clear_dwb_zone()?;
322
323 let mut slot = superblock_slot_image(header_page.as_bytes());
327 self.write_superblock_slot(&mut slot, 0, 1)?;
328 self.write_superblock_slot(&mut slot, 1, 2)?;
329
330 self.sync()?;
332
333 Ok(())
334 }
335
336 fn header_write(&self) -> Result<std::sync::RwLockWriteGuard<'_, DatabaseHeader>, PagerError> {
338 self.header.write().map_err(|_| PagerError::LockPoisoned)
339 }
340
341 fn header_read(&self) -> Result<std::sync::RwLockReadGuard<'_, DatabaseHeader>, PagerError> {
343 self.header.read().map_err(|_| PagerError::LockPoisoned)
344 }
345
346 fn freelist_write(&self) -> Result<std::sync::RwLockWriteGuard<'_, FreeList>, PagerError> {
348 self.freelist.write().map_err(|_| PagerError::LockPoisoned)
349 }
350
351 fn file_lock(&self) -> Result<std::sync::MutexGuard<'_, F>, PagerError> {
353 self.file.lock().map_err(|_| PagerError::LockPoisoned)
354 }
355
356 fn header_dirty_lock(&self) -> Result<std::sync::MutexGuard<'_, bool>, PagerError> {
358 self.header_dirty
359 .lock()
360 .map_err(|_| PagerError::LockPoisoned)
361 }
362
363 fn read_superblock_zone(&self) -> Result<[u8; SUPERBLOCK_ZONE_SIZE], PagerError> {
367 let mut file = self.file_lock()?;
368 Ok(pager_io::read_superblock_zone(&mut *file)?)
369 }
370
371 #[doc(hidden)]
377 pub fn newest_superblock(&self) -> Result<NewestSuperblock, PagerError> {
378 let zone = self.read_superblock_zone()?;
379 let selection = reddb_file::select_paged_superblock(&zone)
380 .ok_or_else(|| PagerError::SuperblockZoneUnrecoverable(self.path.clone()))?;
381
382 let start = selection.copy_index * SUPERBLOCK_SLOT_SIZE;
383 let mut image = [0u8; SUPERBLOCK_SLOT_SIZE];
384 image.copy_from_slice(&zone[start..start + SUPERBLOCK_SLOT_SIZE]);
385 Ok((selection, image))
386 }
387
388 fn ensure_phase3_dwb_zone_layout(&self) -> Result<(), PagerError> {
389 let (_, image) = self.newest_superblock()?;
390 let header = reddb_file::decode_database_header(&image)
391 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
392 if header.version < PHASE3_DWB_ZONE_FILE_VERSION {
393 return Err(PagerError::LegacyPagedStore {
394 path: self.path.clone(),
395 });
396 }
397 Ok(())
398 }
399
400 fn write_superblock_slot(
403 &self,
404 slot: &mut [u8; SUPERBLOCK_SLOT_SIZE],
405 copy_index: usize,
406 generation: u64,
407 ) -> Result<(), PagerError> {
408 if self.config.read_only {
409 return Err(PagerError::ReadOnly);
410 }
411 let mut file = self.file_lock()?;
412 Ok(pager_io::write_superblock_slot(
413 &mut *file, slot, copy_index, generation,
414 )?)
415 }
416
417 #[doc(hidden)]
423 pub fn commit_superblock(
424 &self,
425 mutate: impl FnOnce(&mut [u8; SUPERBLOCK_SLOT_SIZE]) -> Result<(), PagerError>,
426 ) -> Result<(), PagerError> {
427 if self.config.read_only {
428 return Err(PagerError::ReadOnly);
429 }
430 let (newest, mut image) = self.newest_superblock()?;
431 mutate(&mut image)?;
432 let next_copy = reddb_file::paged_superblock_next_copy(Some(newest));
433 self.write_superblock_slot(&mut image, next_copy, newest.generation.saturating_add(1))
434 }
435
436 fn load_header(&self) -> Result<(), PagerError> {
438 let (_, image) = self.newest_superblock()?;
439
440 let mut decoded_header = reddb_file::decode_database_header(&image)
441 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
442 let physical_pages = self.file_lock()?.file_len()?.div_ceil(PAGE_SIZE as u64);
448 let physical_pages = u32::try_from(physical_pages).map_err(|_| {
449 PagerError::InvalidDatabase("data file exceeds the page address space".into())
450 })?;
451 if decoded_header.page_count < physical_pages {
452 decoded_header.page_count = physical_pages;
453 if !self.config.read_only {
454 *self.header_dirty_lock()? = true;
455 }
456 }
457 let freelist_head = decoded_header.freelist_head;
458
459 {
460 let mut header = self.header_write()?;
461 *header = decoded_header;
462 }
463
464 {
466 let mut freelist = self.freelist_write()?;
467 *freelist = FreeList::from_header(freelist_head, 0);
468 }
469
470 Ok(())
471 }
472
473 fn write_header(&self) -> Result<(), PagerError> {
475 if self.config.read_only {
476 return Err(PagerError::ReadOnly);
477 }
478
479 let header = self.header_read()?.clone();
480 self.commit_superblock(|image| {
481 reddb_file::encode_database_header(image, &header)
482 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))
483 })?;
484 *self.header_dirty_lock()? = false;
485
486 Ok(())
487 }
488
489 fn read_page_raw(&self, page_id: u32) -> Result<Page, PagerError> {
491 let mut file = self.file_lock()?;
492 Ok(pager_io::read_page(
493 &mut *file,
494 page_id,
495 self.config.verify_checksums,
496 )?)
497 }
498
499 fn write_page_raw(&self, page_id: u32, page: &Page) -> Result<(), PagerError> {
501 if self.config.read_only {
502 return Err(PagerError::ReadOnly);
503 }
504 let mut file = self.file_lock()?;
505 Ok(pager_io::write_page(&mut *file, page_id, page)?)
506 }
507
508 pub fn read_page(&self, page_id: u32) -> Result<Page, PagerError> {
510 if let Some(page) = self.cache.get(page_id) {
512 return Ok(page);
513 }
514
515 let page = self.read_page_raw(page_id)?;
517
518 if let Some(dirty_page) = self.cache.insert(page_id, page.clone()) {
520 let evicted_id = dirty_page.page_id();
522 self.write_page_raw(evicted_id, &dirty_page)?;
523 }
524
525 Ok(page)
526 }
527
528 pub fn read_page_no_checksum(&self, page_id: u32) -> Result<Page, PagerError> {
533 if let Some(page) = self.cache.get(page_id) {
535 return Ok(page);
536 }
537
538 let mut file = self.file_lock()?;
540 let page = pager_io::read_page(&mut *file, page_id, false)?;
541 drop(file);
542
543 if let Some(dirty_page) = self.cache.insert(page_id, page.clone()) {
545 let evicted_id = dirty_page.page_id();
547 self.write_page_raw(evicted_id, &dirty_page)?;
548 }
549
550 Ok(page)
551 }
552
553 pub fn write_page(&self, page_id: u32, mut page: Page) -> Result<(), PagerError> {
555 if self.config.read_only {
556 return Err(PagerError::ReadOnly);
557 }
558
559 page.update_checksum();
561
562 if let Some(dirty_page) = self.cache.insert(page_id, page) {
567 let evicted_id = dirty_page.page_id();
568 self.write_page_raw(evicted_id, &dirty_page)?;
569 }
570 self.cache.mark_dirty(page_id);
571
572 Ok(())
573 }
574
575 pub fn write_page_no_checksum(&self, page_id: u32, page: Page) -> Result<(), PagerError> {
580 if self.config.read_only {
581 return Err(PagerError::ReadOnly);
582 }
583
584 if let Some(dirty_page) = self.cache.insert(page_id, page) {
587 let evicted_id = dirty_page.page_id();
588 self.write_page_raw(evicted_id, &dirty_page)?;
589 }
590 self.cache.mark_dirty(page_id);
591
592 Ok(())
593 }
594
595 pub fn allocate_page(&self, page_type: PageType) -> Result<Page, PagerError> {
597 if self.config.read_only {
598 return Err(PagerError::ReadOnly);
599 }
600
601 let page_id = {
603 let mut freelist = self.freelist_write()?;
604 self.complete_allocation_fence()?;
607 if let Some(id) = freelist.allocate() {
608 self.allocation_reused
609 .store(true, std::sync::atomic::Ordering::Release);
610 id
611 } else if freelist.trunk_head() != 0 {
612 let trunk_id = freelist.trunk_head();
613
614 let trunk = self.read_page(trunk_id).map_err(|e| match e {
615 PagerError::PageNotFound(_) => {
616 PagerError::InvalidDatabase("Freelist trunk missing".to_string())
617 }
618 other => other,
619 })?;
620
621 freelist
622 .load_from_trunk(&trunk)
623 .map_err(|e| PagerError::InvalidDatabase(format!("Freelist: {}", e)))?;
624 let mut header = self.header_write()?;
625 header.freelist_head = freelist.trunk_head();
626 *self.header_dirty_lock()? = true;
627 drop(header);
628 self.allocation_fence_pending
633 .store(true, std::sync::atomic::Ordering::Release);
634 self.complete_allocation_fence()?;
635 let id = freelist.allocate().ok_or_else(|| {
636 PagerError::InvalidDatabase("Freelist empty after trunk load".to_string())
637 })?;
638 self.allocation_reused
639 .store(true, std::sync::atomic::Ordering::Release);
640 id
641 } else {
642 let mut header = self.header_write()?;
644 let id = header.page_count;
645 header.page_count += 1;
646 *self.header_dirty_lock()? = true;
647 id
648 }
649 };
650
651 let page = Page::new(page_type, page_id);
652
653 if let Some(dirty_page) = self.cache.insert(page_id, page.clone()) {
665 let evicted_id = dirty_page.page_id();
666 self.write_page_raw(evicted_id, &dirty_page)?;
667 }
668 self.cache.mark_dirty(page_id);
669
670 Ok(page)
671 }
672
673 pub fn reserve_contig_extent(&self, n_pages: u32) -> Result<ExtentId, PagerError> {
675 if self.config.read_only {
676 return Err(PagerError::ReadOnly);
677 }
678 if n_pages == 0 {
679 return Err(PagerError::InvalidDatabase(
680 "contiguous extent must reserve at least one page".to_string(),
681 ));
682 }
683
684 let start_page = {
685 let mut header = self.header_write()?;
686 let start = header.page_count;
687 header.page_count = header.page_count.checked_add(n_pages).ok_or_else(|| {
688 PagerError::InvalidDatabase("contiguous extent page count overflow".to_string())
689 })?;
690 *self.header_dirty_lock()? = true;
691 start
692 };
693
694 for page_id in start_page..start_page + n_pages {
695 let mut page = Page::new(PageType::Vector, page_id);
696 page.update_checksum();
697 if let Some(dirty_page) = self.cache.insert(page_id, page) {
698 let evicted_id = dirty_page.page_id();
699 self.write_page_raw(evicted_id, &dirty_page)?;
700 }
701 self.cache.mark_dirty(page_id);
702 }
703
704 Ok(ExtentId {
705 start_page,
706 n_pages,
707 })
708 }
709
710 pub fn free_page(&self, page_id: u32) -> Result<(), PagerError> {
712 if self.config.read_only {
713 return Err(PagerError::ReadOnly);
714 }
715
716 let mut deferred = self
717 .deferred_reclamation
718 .lock()
719 .map_err(|_| PagerError::LockPoisoned)?;
720 if deferred.enabled {
721 if page_id < FIRST_ALLOCATABLE_PAGE || page_id >= self.page_count()? {
722 return Err(PagerError::InvalidDatabase(
723 "invalid deferred free page".into(),
724 ));
725 }
726 deferred.pending.insert(page_id);
729 return Ok(());
730 }
731 drop(deferred);
732
733 self.cache.remove(page_id);
735
736 let mut freelist = self.freelist_write()?;
738 freelist.free(page_id);
739
740 *self.header_dirty_lock()? = true;
741
742 Ok(())
743 }
744
745 pub fn enable_deferred_reclamation(&self) -> Result<(), PagerError> {
746 self.deferred_reclamation
747 .lock()
748 .map_err(|_| PagerError::LockPoisoned)?
749 .enabled = true;
750 Ok(())
751 }
752
753 pub fn deferred_reclamation_pages(&self) -> Result<(Vec<u32>, Vec<u32>), PagerError> {
754 let deferred = self
755 .deferred_reclamation
756 .lock()
757 .map_err(|_| PagerError::LockPoisoned)?;
758 Ok((
759 deferred.pending.iter().copied().collect(),
760 deferred.published.iter().copied().collect(),
761 ))
762 }
763
764 pub fn restore_deferred_reclamation(&self, pages: &[u32]) -> Result<(), PagerError> {
765 let count = self.page_count()?;
766 if pages
767 .iter()
768 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= count)
769 {
770 return Err(PagerError::InvalidDatabase(
771 "invalid persisted deferred free page".into(),
772 ));
773 }
774 let mut deferred = self
775 .deferred_reclamation
776 .lock()
777 .map_err(|_| PagerError::LockPoisoned)?;
778 deferred.pending.extend(pages);
779 deferred.published.extend(pages);
780 Ok(())
781 }
782
783 pub fn complete_deferred_publication(
786 &self,
787 retained: &[u32],
788 released: &[u32],
789 ) -> Result<(), PagerError> {
790 let mut deferred = self
791 .deferred_reclamation
792 .lock()
793 .map_err(|_| PagerError::LockPoisoned)?;
794 if !released.is_empty() {
795 self.free_retired_pages(released)?;
796 }
797 for id in released {
798 deferred.pending.remove(id);
799 }
800 deferred.published = retained.iter().copied().collect();
801 Ok(())
802 }
803
804 pub fn free_retired_pages(&self, pages: &[u32]) -> Result<(), PagerError> {
807 if self.config.read_only {
808 return Err(PagerError::ReadOnly);
809 }
810 let page_count = self.page_count()?;
811 if pages
812 .iter()
813 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= page_count)
814 {
815 return Err(PagerError::InvalidDatabase(
816 "invalid retired page range".into(),
817 ));
818 }
819 let mut freelist = self.freelist_write()?;
820 for id in pages {
821 self.cache.remove(*id);
822 }
823 freelist.free_batch(pages);
824 *self.header_dirty_lock()? = true;
825 Ok(())
826 }
827
828 pub fn free_page_count(&self) -> Result<u32, PagerError> {
831 Ok(self.freelist_write()?.total_free())
832 }
833
834 pub fn header(&self) -> Result<DatabaseHeader, PagerError> {
835 Ok(self.header_read()?.clone())
836 }
837
838 pub fn physical_header(&self) -> Result<PhysicalFileHeader, PagerError> {
839 Ok(self.header_read()?.physical)
840 }
841
842 pub fn update_physical_header(&self, physical: PhysicalFileHeader) -> Result<(), PagerError> {
843 if self.config.read_only {
844 return Err(PagerError::ReadOnly);
845 }
846
847 let mut header = self.header_write()?;
848 header.physical = physical;
849 *self.header_dirty_lock()? = true;
850 Ok(())
851 }
852
853 pub fn page_count(&self) -> Result<u32, PagerError> {
855 Ok(self.header_read()?.page_count)
856 }
857
858 pub fn flush(&self) -> Result<(), PagerError> {
860 if self.config.read_only {
861 return Ok(());
862 }
863
864 let trunks = {
866 let mut freelist = self.freelist_write()?;
867 if freelist.is_dirty() {
868 let mut header = self.header_write()?;
869 let trunks = freelist.flush_to_trunks(0, || {
870 let id = header.page_count;
871 header.page_count += 1;
872 id
873 });
874 header.freelist_head = freelist.trunk_head();
875 *self.header_dirty_lock()? = true;
876 freelist.mark_clean();
877 trunks
878 } else {
879 Vec::new()
880 }
881 };
882
883 let mut dirty_pages = Vec::new();
884 for trunk in trunks {
885 let page_id = trunk.page_id();
886 if let Some(evicted) = self.cache.insert(page_id, trunk) {
887 dirty_pages.push((evicted.page_id(), evicted));
890 }
891 self.cache.mark_dirty(page_id);
892 }
893
894 dirty_pages.extend(self.cache.flush_dirty());
896 if !dirty_pages.is_empty() {
897 let max_lsn = dirty_pages
902 .iter()
903 .filter_map(|(_, page)| page.header().ok().map(|h| h.lsn))
904 .max()
905 .unwrap_or(0);
906 if max_lsn > 0 {
907 self.wal.flush_until(max_lsn)?;
908 }
909 self.write_pages_through_dwb(&dirty_pages)?;
910 }
911
912 if *self.header_dirty_lock()? {
914 self.write_header()?;
915 }
916
917 Ok(())
918 }
919
920 pub fn sync(&self) -> Result<(), PagerError> {
922 self.flush()?;
923
924 if self
925 .allocation_reused
926 .swap(false, std::sync::atomic::Ordering::AcqRel)
927 {
928 if let Err(error) = self.write_header_and_sync() {
932 self.allocation_reused
933 .store(true, std::sync::atomic::Ordering::Release);
934 return Err(error);
935 }
936 }
937
938 let file = self.file_lock()?;
939 file.sync_all()?;
940
941 Ok(())
942 }
943
944 pub fn cache_stats(&self) -> super::page_cache::CacheStats {
946 self.cache.stats()
947 }
948
949 pub fn cache_len(&self) -> usize {
952 self.cache.len()
953 }
954
955 pub fn cache_capacity(&self) -> usize {
958 self.cache.capacity()
959 }
960
961 pub fn dirty_page_count(&self) -> usize {
963 self.cache.dirty_count()
964 }
965
966 pub fn dirty_fraction(&self) -> f64 {
970 let capacity = self.cache.capacity().max(1) as f64;
971 self.cache.dirty_count() as f64 / capacity
972 }
973
974 pub fn flush_some_dirty(&self, max: usize) -> Result<usize, PagerError> {
978 if self.config.read_only || max == 0 {
979 return Ok(0);
980 }
981 let dirty_pages = self.cache.flush_some_dirty(max);
982 if dirty_pages.is_empty() {
983 return Ok(0);
984 }
985 let count = dirty_pages.len();
986 for (page_id, page) in dirty_pages {
993 self.write_page(page_id, page)?;
994 }
995 Ok(count)
996 }
997
998 pub fn path(&self) -> &Path {
1000 &self.path
1001 }
1002
1003 pub fn is_read_only(&self) -> bool {
1005 self.config.read_only
1006 }
1007
1008 pub fn file_size(&self) -> Result<u64, PagerError> {
1010 let file = self.file_lock()?;
1011 Ok(file.file_len()?)
1012 }
1013
1014 fn clear_dwb_zone(&self) -> Result<(), PagerError> {
1016 let mut file = self.file_lock()?;
1017 Ok(pager_io::clear_dwb_zone(&mut *file)?)
1018 }
1019
1020 pub fn read_manifest_page(&self) -> Result<Page, PagerError> {
1025 self.read_page(1)
1026 .map_err(|_| PagerError::ManifestZoneCorrupt(self.path.clone()))
1027 }
1028
1029 fn write_pages_through_dwb(&self, pages: &[(u32, Page)]) -> Result<(), PagerError> {
1036 if self.config.read_only {
1037 return Err(PagerError::ReadOnly);
1038 }
1039 let mut file = self.file_lock()?;
1042 Ok(pager_io::write_pages(&mut *file, pages, self.dwb_enabled)?)
1043 }
1044
1045 fn recover_from_dwb(&self) -> Result<(), PagerError> {
1050 let mut file = self.file_lock()?;
1051 Ok(pager_io::recover_from_dwb(&mut *file)?)
1052 }
1053
1054 fn complete_allocation_fence(&self) -> Result<(), PagerError> {
1058 if self
1059 .allocation_fence_pending
1060 .load(std::sync::atomic::Ordering::Acquire)
1061 {
1062 self.write_header_and_sync()?;
1063 self.write_header_and_sync()?;
1064 self.allocation_fence_pending
1065 .store(false, std::sync::atomic::Ordering::Release);
1066 }
1067 Ok(())
1068 }
1069
1070 pub fn write_header_and_sync(&self) -> Result<(), PagerError> {
1072 self.write_header()?;
1073 let file = self.file_lock()?;
1074 file.sync_all()?;
1075 Ok(())
1076 }
1077
1078 pub fn set_checkpoint_in_progress(
1080 &self,
1081 in_progress: bool,
1082 target_lsn: u64,
1083 ) -> Result<(), PagerError> {
1084 let mut header = self.header_write()?;
1085 header.checkpoint_in_progress = in_progress;
1086 header.checkpoint_target_lsn = target_lsn;
1087 *self.header_dirty_lock()? = true;
1088 drop(header);
1089 self.write_header_and_sync()
1090 }
1091
1092 pub fn complete_checkpoint(&self, lsn: u64) -> Result<(), PagerError> {
1094 let mut header = self.header_write()?;
1095 header.checkpoint_lsn = lsn;
1096 header.checkpoint_in_progress = false;
1097 header.checkpoint_target_lsn = 0;
1098 *self.header_dirty_lock()? = true;
1099 drop(header);
1100 self.write_header_and_sync()
1101 }
1102}