///|
priv struct FreelistCursor {
db : Database
claim : (Int, PageKind, Int?) -> Unit raise SqliteError
trace : InspectionTrace
result : Array[Int]
visited : Map[Int, Bool]
mut next_page : Int
mut supplied : Bytes?
mut done : Bool
}
///|
fn Database::freelist_cursor(
self : Database,
claim : (Int, PageKind, Int?) -> Unit raise SqliteError,
trace : InspectionTrace,
) -> FreelistCursor raise SqliteError {
trace.at(FreelistCheck, Some(1), byte_offset=Some(32))
{
db: self,
claim,
trace,
result: [],
visited: Map([]),
next_page: page_number(self.header.freelist_trunk),
supplied: None,
done: false,
}
}
///|
fn FreelistCursor::next(self : FreelistCursor) -> Int? raise SqliteError {
if self.done {
return None
}
while self.next_page != 0 {
let number = self.next_page
if number == 1 {
raise Invalid("schema 页不能出现在 freelist")
}
if self.visited.contains(number) {
raise Invalid("freelist 页重复或形成环")
}
if self.result.length() >= self.db.limits.max_pages {
raise LimitExceeded("freelist 页数超过限制")
}
if self.supplied is None {
self.trace.at(ReadPage, Some(number))
return Some(number)
}
let bytes = self.supplied.unwrap()
self.supplied = None
self.trace.at(PageOwnershipCheck, Some(number))
(self.claim)(number, FreelistTrunk, None)
self.visited[number] = true
self.result.push(number)
self.trace.at(FreelistCheck, Some(number), byte_offset=Some(4))
let count = page_number(read_u32(bytes, 4))
if count > (self.db.header.usable_size - 8) / 4 {
raise Invalid("freelist trunk 指针数组越界")
}
for i = 0; i < count; i = i + 1 {
self.trace.at(FreelistCheck, Some(number), byte_offset=Some(8 + i * 4))
let leaf = page_number(read_u32(bytes, 8 + i * 4))
if leaf <= 1 || leaf > self.db.page_count || self.visited.contains(leaf) {
raise Invalid("freelist leaf 越界或重复")
}
if self.result.length() >= self.db.limits.max_pages {
raise LimitExceeded("freelist 页数超过限制")
}
self.visited[leaf] = true
(self.claim)(leaf, FreelistLeaf, None)
self.result.push(leaf)
}
self.trace.at(FreelistCheck, Some(number), byte_offset=Some(0))
self.next_page = page_number(read_u32(bytes, 0))
}
self.trace.at(FreelistCheck, Some(1), byte_offset=Some(36))
if self.result.length().reinterpret_as_uint() != self.db.header.freelist_pages {
raise Invalid("freelist 长度与数据库头不一致")
}
self.done = true
None
}
///|
priv enum PtrmapEvent {
MapPage(Int)
MapEntry(PtrmapEntry)
MapFinished
}
///|
priv struct PtrmapCursor {
db : Database
trace : InspectionTrace
mut number : Int
mut cached_number : Int
mut cached : Bytes
}
///|
fn Database::ptrmap_cursor(
self : Database,
trace : InspectionTrace,
) -> PtrmapCursor raise SqliteError {
trace.at(PtrmapCheck, Some(1), byte_offset=Some(52))
if self.header.largest_root_page == 0U && self.header.incremental_vacuum {
raise Invalid("未启用 auto-vacuum 却设置 incremental-vacuum")
}
if self.header.largest_root_page.to_uint64() > self.page_count.to_uint64() {
raise Invalid("largest root page 越界")
}
if self.header.largest_root_page != 0U &&
self.page_count > self.limits.max_pages {
raise LimitExceeded("Ptrmap 枚举超过页数预算")
}
{ db: self, trace, number: 2, cached_number: 0, cached: b"", }
}
///|
fn PtrmapCursor::next(self : PtrmapCursor) -> PtrmapEvent raise SqliteError {
if self.db.header.largest_root_page == 0U {
return MapFinished
}
let lock_page = 1073741824 / self.db.header.page_size + 1
while self.number <= self.db.page_count {
let number = self.number
let map_page = ptrmap_page_number(
number,
self.db.header.usable_size,
self.db.header.page_size,
)
if number == lock_page || number == map_page {
self.number = self.number + 1
continue
}
if map_page != self.cached_number {
self.trace.at(ReadPage, Some(map_page))
return MapPage(map_page)
}
let offset = (number - map_page - 1) * 5
self.trace.at(PtrmapCheck, Some(map_page), byte_offset=Some(offset))
if offset < 0 || offset > self.db.header.usable_size - 5 {
raise Invalid("Ptrmap 条目位置越界")
}
let kind = match self.cached[offset].to_int() {
1 => RootPage
2 => FreePage
3 => FirstOverflow
4 => NextOverflow
5 => NonRootBtree
_ => raise Invalid("页 \{number} 的 Ptrmap 类型无效")
}
self.trace.at(PtrmapCheck, Some(map_page), byte_offset=Some(offset + 1))
let parent = page_number(read_u32(self.cached, offset + 1))
let zero_parent = kind == RootPage || kind == FreePage
if (zero_parent && parent != 0) ||
(
!zero_parent &&
(parent < 1 || parent > self.db.page_count || parent == number)
) {
raise Invalid("页 \{number} 的 Ptrmap 父页无效")
}
self.trace.at(PtrmapCheck, Some(map_page), byte_offset=Some(offset))
self.number = self.number + 1
return MapEntry({
page_number: number,
map_page,
kind,
parent_page: parent,
})
}
MapFinished
}