///|
/// 按顺序供给完整 WAL header 或帧;不执行宿主 I/O。
/// WAL 供给协议:NeedWalRange 指定准确 64 位偏移及完整 header/帧长度;WalReady 返回验证报告。
pub(all) enum WalReadEvent {
NeedWalRange(Int64, Int)
WalReady(RangeWalInspection)
} derive(Debug)
///|
pub extend WalReadEvent with @debug.Debug::{to_repr}
///|
/// 不执行 I/O 的顺序 WAL 校验游标;供给范围必须符合当前请求。取消或失败的游标不能用于构造覆盖快照。
pub struct WalCursor {
priv size : Int64
priv max_frames : Int
priv mut header : WalHeader?
priv frames : Array[RangeWalFrame]
priv commits : Array[RangeWalCommit]
priv mut offset : Int64
priv mut checksum : (UInt, UInt)
priv mut committed_frames : Int
priv mut database_pages : UInt?
priv mut requested : (Int64, Int)?
priv mut supplied : Bytes?
priv mut finished : RangeWalInspection?
priv mut closed : Bool
}
///|
/// 创建顺序 WAL 校验游标,不读取数据;size 为非负 Int64,max_frames 默认为 100000 且必须为正,非空短 header 立即拒绝。
pub fn WalCursor::new(
size : Int64,
max_frames? : Int = 100000,
) -> WalCursor raise SqliteError {
if max_frames < 1 {
raise Invalid("WAL 帧上限必须为正数")
}
if size < 0L {
raise Invalid("WAL 数据源长度不能为负数")
}
if size > 0L && size < 32L {
raise Invalid("WAL header 不完整")
}
{
size,
max_frames,
header: None,
frames: [],
commits: [],
offset: 0L,
checksum: (0U, 0U),
committed_frames: 0,
database_pages: None,
requested: None,
supplied: None,
finished: None,
closed: false,
}
}
///|
/// 只接受当前请求,调用者须先分类宿主短读、越界与读取失败。
/// 只接受当前 NeedWalRange 的准确偏移和长度;错位、短读、重复供给或已关闭抛 Invalid。先由宿主分类读取错误。
pub fn WalCursor::provide(
self : WalCursor,
offset : Int64,
bytes : Bytes,
) -> Unit raise SqliteError {
if self.closed || self.finished is Some(_) || self.supplied is Some(_) {
raise Invalid("WAL 游标已关闭、完成或已经供给数据")
}
match self.requested {
Some((expected, count)) =>
if offset != expected || bytes.length() != count {
raise Invalid("WAL 供给范围不符合当前请求")
}
None => raise Invalid("WAL 游标尚未请求数据")
}
self.supplied = Some(bytes)
}
///|
/// 取消后释放缓冲和索引;不能把此前的提交当成最新快照返回。
/// 取消并释放供给缓冲、帧和提交数组;取消后的游标不能构造覆盖快照。
pub fn WalCursor::close(self : WalCursor) -> Unit {
self.closed = true
self.supplied = None
self.requested = None
self.frames.clear()
self.commits.clear()
self.finished = None
}
///|
/// 返回已校验有效帧数;未完成时不能当作最新完整 WAL 的边界,close 后计数归零。
pub fn WalCursor::frames_read(self : WalCursor) -> Int {
self.frames.length()
}
///|
fn copy_wal_inspection(report : RangeWalInspection) -> RangeWalInspection {
{ ..report, frames: report.frames.copy(), commits: report.commits.copy(), }
}
///|
fn WalCursor::finish(self : WalCursor, reason : WalStopReason) -> WalReadEvent {
let report : RangeWalInspection = {
header: self.header,
frames: self.frames,
commits: self.commits,
committed_frames: self.committed_frames,
database_pages: self.database_pages,
stop_reason: reason,
stop_offset: self.offset,
trailing_bytes: self.size - self.offset,
}
self.finished = Some(report)
WalReady(copy_wal_inspection(report))
}
///|
/// 未供给数据时重复同一请求,成功接受一帧后才推进 offset 和 checksum。
/// 顺序推进至完整 header/帧请求或验证报告;等待供给时重复请求不变,失败后关闭游标。异常尾部记录在停止原因。
pub fn WalCursor::next(self : WalCursor) -> WalReadEvent raise SqliteError {
if self.closed {
raise Invalid("WAL 游标已经关闭")
}
errdefer self.close()
if self.finished is Some(report) {
return WalReady(copy_wal_inspection(report))
}
if self.requested is Some((offset, count)) && self.supplied is None {
return NeedWalRange(offset, count)
}
if self.size == 0L {
return self.finish(WalEndOfFile)
}
if self.header is None {
if self.supplied is None {
self.requested = Some((0L, 32))
return NeedWalRange(0L, 32)
}
let header = parse_wal_header(self.supplied.unwrap())
self.header = Some(header)
self.checksum = (header.checksum1, header.checksum2)
self.offset = 32L
self.supplied = None
self.requested = None
} else if self.supplied is Some(data) {
let header = self.header.unwrap()
self.supplied = None
self.requested = None
if read_u32(data, 8) != header.salt1 || read_u32(data, 12) != header.salt2 {
return self.finish(WalSaltMismatch)
}
let number = read_u32(data, 0)
let pages = read_u32(data, 4)
if number == 0U || number == 0xffffffffU || pages == 0xffffffffU {
return self.finish(WalInvalidFrame)
}
let next = wal_checksum(data, 0, 8, header.checksum_order, self.checksum)
let next = wal_checksum(
data,
24,
header.page_size,
header.checksum_order,
next,
)
if next != (read_u32(data, 16), read_u32(data, 20)) {
return self.finish(WalChecksumMismatch)
}
let index = self.frames.length() + 1
self.frames.push({
index,
byte_offset: self.offset,
page_number: number,
database_pages: pages,
checksum1: next.0,
checksum2: next.1,
})
if pages != 0U {
self.commits.push({ frame_index: index, database_pages: pages, })
self.committed_frames = index
self.database_pages = Some(pages)
}
self.checksum = next
self.offset = self.offset + 24L + header.page_size.to_int64()
}
if self.offset == self.size {
return self.finish(WalEndOfFile)
}
if self.frames.length() == self.max_frames {
return self.finish(WalFrameLimit)
}
let count = 24 + self.header.unwrap().page_size
if self.size - self.offset < count.to_int64() {
return self.finish(WalTruncatedFrame)
}
self.requested = Some((self.offset, count))
NeedWalRange(self.offset, count)
}