///|
/// 全库归属报告的八类页用途;一页只统计首次归属,冲突另外报告。
pub(all) enum PageKind {
BtreeRoot
BtreeChild
OverflowFirst
OverflowContinuation
FreelistTrunk
FreelistLeaf
PointerMap
LockByte
} derive(Debug, Eq)
///|
pub extend PageKind with @debug.Debug::{to_repr}
///|
pub extend PageKind with Eq::{equal, not_equal}
///|
/// 首次认领页面的用途、根页、对象和父页;不属于对象或没有父页时相应字段为 None。
pub(all) struct PageOwnership {
page_number : Int
kind : PageKind
root_page : Int?
object_name : String?
parent_page : Int?
} derive(Debug, Eq)
///|
pub extend PageOwnership with @debug.Debug::{to_repr}
///|
pub extend PageOwnership with Eq::{equal, not_equal}
///|
/// 全库结构问题:扫描错误、跨用途页冲突、完整覆盖后的未认领页、不支持特性及 Ptrmap 归属不一致。
pub(all) enum DatabaseIssue {
ScanError(Int?, SqliteError)
PageConflict(PageOwnership, PageOwnership)
UnclaimedPage(Int)
UnsupportedFeature(String)
PtrmapMismatch(PtrmapEntry, PageOwnership)
} derive(Debug)
///|
pub extend DatabaseIssue with @debug.Debug::{to_repr}
///|
/// 只检查逻辑数据库页;完整页归属不等价于 SQL 完整性检查。
/// 全库已观察报告。ownership_complete 与 ptrmap_checked 分别标记覆盖;diagnostics_truncated 表示诊断截断。schema 记录计入 records_decoded,失败 payload 请求也计费。
pub(all) struct DatabaseInspection {
status : InspectionStatus
ownership_complete : Bool
ptrmap_checked : Bool
diagnostics_truncated : Bool
roots_inspected : Int
records_decoded : Int
payload_bytes : UInt64
pages : Array[PageOwnership]
unclaimed_pages : Array[Int]
issues : Array[DatabaseIssue]
} derive(Debug)
///|
pub extend DatabaseInspection with @debug.Debug::{to_repr}
///|
priv struct OwnershipState {
owners : Map[Int, PageOwnership]
issues : Array[DatabaseIssue]
located_issues : Array[LocatedDatabaseIssue]
trace : InspectionTrace
max_pages : Int
max_issues : Int
mut coverage : Bool
mut failed : Bool
mut truncated : Bool
mut roots : Int
mut records : Int
mut payload : UInt64
}
///|
fn OwnershipState::issue(self : OwnershipState, issue : DatabaseIssue) -> Unit {
if self.issues.length() < self.max_issues {
let location : DiagnosticLocation? = match issue {
PageConflict(_, owner) =>
Some({
phase: PageOwnershipCheck,
page_number: Some(owner.page_number),
byte_offset: None,
cell_index: None,
page_diagnostic: None,
})
UnclaimedPage(number) =>
Some({
phase: PageOwnershipCheck,
page_number: Some(number),
byte_offset: None,
cell_index: None,
page_diagnostic: None,
})
UnsupportedFeature(_) => None
_ => Some(self.trace.snapshot())
}
self.issues.push(issue)
self.located_issues.push({ issue, location, })
} else {
self.truncated = true
}
}
///|
fn OwnershipState::error(
self : OwnershipState,
root : Int?,
error : SqliteError,
) -> Unit {
self.coverage = false
if error is Invalid(_) {
self.failed = true
}
self.issue(ScanError(root, error))
}
///|
fn OwnershipState::claim(
self : OwnershipState,
owner : PageOwnership,
) -> Unit raise SqliteError {
if self.owners.get(owner.page_number) is Some(previous) {
self.failed = true
self.issue(PageConflict(previous, owner))
raise Invalid("页 \{owner.page_number} 被多个用途或对象占用")
}
if self.owners.length() >= self.max_pages {
raise LimitExceeded("数据库级页数预算已耗尽")
}
self.owners[owner.page_number] = owner
}
///|
fn OwnershipState::observer(
self : OwnershipState,
root : Int,
name : String,
) -> ScanObserver {
{
claim: (number, kind, parent) => {
self.claim({
page_number: number,
kind,
root_page: Some(root),
object_name: Some(name),
parent_page: parent,
})
},
charge_payload: size => self.payload = self.payload + size,
trace: self.trace,
}
}
///|
fn OwnershipState::finish(
self : OwnershipState,
page_count : Int,
ptrmap_checked : Bool,
) -> DatabaseInspection {
let pages : Array[PageOwnership] = []
let unclaimed : Array[Int] = []
// 预检拒绝超预算的数据库,避免枚举或分配无界的页列表。
if page_count <= self.max_pages {
for index = 0; index < page_count; index = index + 1 {
let number = index + 1
if self.owners.get(number) is Some(owner) {
pages.push(owner)
} else {
unclaimed.push(number)
if self.coverage {
self.failed = true
self.issue(UnclaimedPage(number))
}
}
}
}
let status = if self.failed {
Failed
} else if !self.coverage || !ptrmap_checked || self.truncated {
Incomplete
} else {
Complete
}
{
status,
ownership_complete: self.coverage,
ptrmap_checked,
diagnostics_truncated: self.truncated,
roots_inspected: self.roots,
records_decoded: self.records,
payload_bytes: self.payload,
pages,
unclaimed_pages: unclaimed,
issues: self.issues,
}
}
///|
/// 扫描 schema、独立 B-tree 和 freelist,使用整个检查过程共享的预算。
/// 检查 schema、对象树、freelist 和 Ptrmap 的全局归属;保存部分结果。默认累计 payload 64 MiB、最多 100 条诊断,不检查 SQL 排序或表/索引内容一致性。
pub fn Database::inspect_database(
self : Database,
max_total_payload_bytes? : UInt64 = 67108864UL,
max_issues? : Int = 100,
) -> DatabaseInspection {
self.inspect_database_traced([], max_total_payload_bytes~, max_issues~)
}
///|
fn Database::inspect_database_traced(
self : Database,
located_issues : Array[LocatedDatabaseIssue],
max_total_payload_bytes~ : UInt64,
max_issues~ : Int,
) -> DatabaseInspection {
let cursor = self.inspection_cursor_traced(
located_issues,
max_total_payload_bytes~,
max_issues~,
)
while true {
match cursor.next() {
NeedInspectionPage(number) =>
cursor.provide_page(number, self.read_page(number)) catch {
error =>
cursor.reject_page(error) catch {
_ => abort("全库游标请求状态不一致")
}
}
InspectionProgress(_) => ()
InspectionFinished(details) => return details.inspection
}
} nobreak {
abort("全库检查循环意外结束")
}
}