Merge 'check freelist count in integrity check' from Jussi Saurio

Closes #3003
This commit is contained in:
Pekka Enberg
2025-09-10 16:15:39 +03:00
committed by GitHub
2 changed files with 39 additions and 4 deletions

View File

@@ -5518,6 +5518,13 @@ pub enum IntegrityCheckError {
references: Vec<u64>,
page_category: PageCategory,
},
#[error(
"Freelist count mismatch. actual_count={actual_count}, expected_count={expected_count}"
)]
FreelistCountMismatch {
actual_count: usize,
expected_count: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -5528,6 +5535,12 @@ pub(crate) enum PageCategory {
FreePage,
}
#[derive(Clone)]
pub struct CheckFreelist {
pub expected_count: usize,
pub actual_count: usize,
}
#[derive(Clone)]
struct IntegrityCheckPageEntry {
page_idx: usize,
@@ -5540,6 +5553,7 @@ pub struct IntegrityCheckState {
first_leaf_level: Option<usize>,
page_reference: HashMap<u64, u64>,
page: Option<PageRef>,
pub freelist_count: CheckFreelist,
}
impl IntegrityCheckState {
@@ -5549,9 +5563,17 @@ impl IntegrityCheckState {
page_reference: HashMap::new(),
first_leaf_level: None,
page: None,
freelist_count: CheckFreelist {
expected_count: 0,
actual_count: 0,
},
}
}
pub fn set_expected_freelist_count(&mut self, count: usize) {
self.freelist_count.expected_count = count;
}
pub fn start(
&mut self,
page_idx: usize,
@@ -5585,10 +5607,7 @@ impl IntegrityCheckState {
) {
let page_id = entry.page_idx as u64;
let Some(previous) = self.page_reference.insert(page_id, referenced_by) else {
// do not traverse free pages as they have no meaingful structured content
if entry.page_category != PageCategory::FreePage {
self.page_stack.push(entry);
}
self.page_stack.push(entry);
return;
};
errors.push(IntegrityCheckError::PageReferencedMultipleTimes {
@@ -5647,6 +5666,7 @@ pub fn integrity_check(
let contents = page.get_contents();
if page_category == PageCategory::FreeListTrunk {
state.freelist_count.actual_count += 1;
let next_freelist_trunk_page = contents.read_u32_no_offset(0);
if next_freelist_trunk_page != 0 {
state.push_page(
@@ -5676,6 +5696,10 @@ pub fn integrity_check(
}
continue;
}
if page_category == PageCategory::FreePage {
state.freelist_count.actual_count += 1;
continue;
}
if page_category == PageCategory::Overflow {
let next_overflow_page = contents.read_u32_no_offset(0);
if next_overflow_page != 0 {

View File

@@ -7363,6 +7363,9 @@ pub fn op_integrity_check(
let mut current_root_idx = 0;
// check freelist pages first, if there are any for database
if freelist_trunk_page > 0 {
let expected_freelist_count =
return_if_io!(pager.with_header(|header| header.freelist_pages.get()));
integrity_check_state.set_expected_freelist_count(expected_freelist_count as usize);
integrity_check_state.start(
freelist_trunk_page as usize,
PageCategory::FreeListTrunk,
@@ -7389,6 +7392,14 @@ pub fn op_integrity_check(
*current_root_idx += 1;
return Ok(InsnFunctionStepResult::Step);
} else {
if integrity_check_state.freelist_count.actual_count
!= integrity_check_state.freelist_count.expected_count
{
errors.push(IntegrityCheckError::FreelistCountMismatch {
actual_count: integrity_check_state.freelist_count.actual_count,
expected_count: integrity_check_state.freelist_count.expected_count,
});
}
let message = if errors.is_empty() {
"ok".to_string()
} else {