///|
/// 异常尾部策略:默认 RejectInvalidTail;显式 UseValidPrefix 使用此前完整提交。帧预算不足或宿主读取失败不能降级为旧快照。
pub(all) enum WalTailPolicy {
RejectInvalidTail
UseValidPrefix
} derive(Debug, Eq)
///|
pub extend WalTailPolicy with @debug.Debug::{to_repr}
///|
pub extend WalTailPolicy with Eq::{equal, not_equal}
///|
/// 持有完整 WAL 字节的旧 PageSource 覆盖视图;读取最新完整提交,隔离未提交更新及缩小/增长后的旧页,受 Int 地址空间限制。
pub struct WalSource {
base : &PageSource
wal : Bytes
page_size : Int
page_count : Int
base_pages : Int
overlay : Map[Int, Int]
inspection : WalInspection
}
///|
pub extend WalSource with PageSource::{byte_length, read_range}
///|
/// 只读最新已提交快照;调用者负责提供同一数据库、同一时刻的不可变副本。
/// 使用完整 WAL 字节构造旧 Int 地址空间覆盖视图;默认拒绝异常尾部,显式有效前缀策略也不能绕过帧预算。配对源必须静态一致。
pub fn WalSource::new(
base : &PageSource,
wal : Bytes,
tail_policy? : WalTailPolicy = RejectInvalidTail,
max_frames? : Int = 100000,
) -> WalSource raise SqliteError {
let inspection = inspect_wal(wal, max_frames~)
if inspection.stop_reason == WalFrameLimit {
raise LimitExceeded(
"WAL 帧上限不足,不能确定最新已提交快照",
)
}
if tail_policy == RejectInvalidTail && inspection.stop_reason != WalEndOfFile {
raise Invalid(
"WAL 存在无效或截断尾部;请检查报告并显式选择 UseValidPrefix",
)
}
let size = base.byte_length()
if size < 0 {
raise Invalid("主文件长度不能为负数")
}
let base_header = if size == 0 {
None
} else {
Some(parse_header(source_read(base, 0, 100)))
}
let page_size = match inspection.header {
Some(header) => header.page_size
None =>
base_header.unwrap_or_else(() => {
raise Invalid("没有 WAL 或可用主文件")
}).page_size
}
if size % page_size != 0 {
raise Invalid("WAL 主文件不包含完整页面")
}
if base_header is Some(header) && header.page_size != page_size {
raise Invalid("WAL 与主文件 page_size 不一致")
}
let physical_pages = size / page_size
let mut base_pages = physical_pages
let page_count = match inspection.database_pages {
Some(pages) => bounded_int(pages.to_uint64(), "WAL database pages")
None => {
let header = base_header.unwrap_or_else(() => {
raise Invalid("没有已提交 WAL 或可用主文件")
})
if header.declared_pages != 0U &&
header.change_counter == header.version_valid_for {
let count = bounded_int(
header.declared_pages.to_uint64(),
"database pages",
)
if count > physical_pages {
raise Invalid("主文件声明页数超过文件长度")
}
count
} else {
physical_pages
}
}
}
if page_count < 1 || page_count > 2147483647 / page_size {
raise LimitExceeded("WAL 快照大小超出 Int 数据源范围")
}
let overlay : Map[Int, Int] = Map([])
// 逆序保留最新版本;后续提交的缩小边界使更早的高页号版本失效。
// 只扫描一次,避免每个提交反复遍历整张覆盖表。
let mut retained_pages = page_count
for i = inspection.committed_frames - 1; i >= 0; i = i - 1 {
let frame = inspection.frames[i]
if frame.database_pages != 0U &&
frame.database_pages.to_uint64() < retained_pages.to_uint64() {
retained_pages = frame.database_pages.reinterpret_as_int()
}
if frame.page_number.to_uint64() <= retained_pages.to_uint64() {
let number = frame.page_number.reinterpret_as_int()
if !overlay.contains(number) {
overlay[number] = frame.byte_offset + 24
}
}
}
base_pages = Int::min(base_pages, retained_pages)
// 文件增长的所有新页必须由 WAL 提供;避免从旧的截断残留页读取数据。
let mut new_pages = 0
for number, _ in overlay {
if number > base_pages {
new_pages = new_pages + 1
}
}
if page_count > base_pages && new_pages != page_count - base_pages {
raise Invalid("WAL 快照缺少增长后的页面")
}
{ base, wal, page_size, page_count, base_pages, overlay, inspection, }
}
///|
/// 返回构造覆盖视图时的 WAL 验证报告,不重新扫描源;调用者不应修改报告数组。
pub fn WalSource::inspection(self : WalSource) -> WalInspection {
self.inspection
}
///|
/// 返回最新完整提交的逻辑页数;没有提交时使用主文件逻辑页数。
pub fn WalSource::page_count(self : WalSource) -> Int {
self.page_count
}
///|
/// 返回静态源字节长度,不执行格式检查。
pub impl PageSource for WalSource with fn byte_length(self) {
self.page_count * self.page_size
}
///|
/// 按源契约返回准确范围字节;范围和长度错误按实现所属的源错误类型返回。
pub impl PageSource for WalSource with fn read_range(self, offset, count) {
let size = self.byte_length()
if offset < 0 || count < 0 || offset > size || count > size - offset {
raise Invalid("WAL 快照读取越界")
}
let output : FixedArray[Byte] = FixedArray::make(count, 0)
let mut copied = 0
while copied < count {
let position = offset + copied
let number = position / self.page_size + 1
let in_page = position % self.page_size
let length = Int::min(count - copied, self.page_size - in_page)
let input = match self.overlay.get(number) {
Some(wal_offset) =>
self.wal[wal_offset + in_page:wal_offset + in_page + length].to_owned()
None => {
if number > self.base_pages {
raise Invalid("WAL 快照页面缺失")
}
source_read(self.base, position, length)
}
}
for i = 0; i < length; i = i + 1 {
output[copied + i] = input[i]
}
copied = copied + length
}
Bytes::from_array(output)
}
///|
/// 从已构造的 WAL 覆盖源打开只读 Database;使用提交逻辑页数,包括缩小和增长,不把旧主文件页数当快照页数。
pub fn open_wal_source(
source : WalSource,
limits? : Limits = Limits::default(),
) -> Database raise SqliteError {
open_sized_source(source, limits, Some(source.page_count))
}
///|
/// 便捷打开完整静态 db/WAL 字节;不是在线锁、checkpoint 或恢复协议。默认严格尾部策略,空 WAL 使用主文件。
pub fn open_wal_database(
base : Bytes,
wal : Bytes,
limits? : Limits = Limits::default(),
tail_policy? : WalTailPolicy = RejectInvalidTail,
max_frames? : Int = 100000,
) -> Database raise SqliteError {
open_wal_source(
WalSource::new(BytesSource::new(base), wal, tail_policy~, max_frames~),
limits~,
)
}