///|
/// 帧:kind(1=Data, 2=Commit)、txn_id(Int, LE u64)、page_id(LE u32)、payload、crc32(LE u32)。
///
/// 线格式固定 16 字节头 + 负载 + 4 字节 CRC:Data 负载 4096 字节页,Commit 负载 0 字节。
/// CRC 覆盖头部和负载,不含 CRC 自身。
/// `encode_wal` 精确预分配整段日志,保持版本 1 的线格式。
priv enum Frame {
Data(txn_id~ : Int, page_id~ : Int, page~ : Bytes)
Commit(txn_id~ : Int)
}
///|
fn frame_kind(frame : Frame) -> UInt {
match frame {
Data(_) => 1U
Commit(_) => 2U
}
}
///|
fn frame_txn_id(frame : Frame) -> Int {
match frame {
Data(txn_id~, ..) | Commit(txn_id~) => txn_id
}
}
///|
fn frame_page_id(frame : Frame) -> Int {
match frame {
Data(page_id~, ..) => page_id
Commit(_) => 0
}
}
///|
fn frame_payload(frame : Frame) -> Bytes {
match frame {
Data(page~, ..) => page
Commit(_) => b""
}
}
///|
/// 单帧字节数:16 字节头 + 负载 + 4 字节 CRC。
fn frame_len(frame : Frame) -> Int {
16 + frame_payload(frame).length() + 4
}
///|
/// 编码整段 WAL。精确预分配一次,逐帧写入,不做累加拼接。
fn encode_wal(frames : Array[Frame]) -> Bytes {
let mut total = 0
for frame in frames {
total = total + frame_len(frame)
}
if total == 0 {
return b""
}
let buf = FixedArray::make(total, b'\x00')
let mut offset = 0
for frame in frames {
let payload = frame_payload(frame)
write_u32(buf, offset, frame_kind(frame))
write_u64_int(buf, offset + 4, frame_txn_id(frame))
write_u32(buf, offset + 12, frame_page_id(frame).reinterpret_as_uint())
buf.blit_from_bytes(offset + 16, payload, 0, payload.length())
// This borrowed alias is read completely before the buffer is mutated again.
let checksum = crc32(
buf.unsafe_reinterpret_as_bytes()[offset:offset + 16 + payload.length()],
)
write_u32(buf, offset + 16 + payload.length(), checksum)
offset = offset + 16 + payload.length() + 4
}
Bytes::from_array(buf[:])
}
///|
/// 重放 WAL,返回所有已提交事务写入的页:页 id 到 4096 字节页。
///
/// 契约(调用方按此分支处理):
/// 不完整的已知帧尾部被忽略;完整帧的字段或 CRC 损坏抛 `Corrupt`。
/// 不重新同步。未提交帧不进入结果;同一页最后一次提交的值生效。
fn replay_wal(wal : Bytes) -> Map[Int, Bytes] raise DbError {
// 挂起的事务:txn_id -> (页 id, 页视图)。视图在提交前不复制。
let pending : Map[Int, Map[Int, BytesView]] = Map([])
let committed : Map[Int, Bytes] = Map([])
let mut offset = 0
while offset + 20 <= wal.length() {
let kind = read_u32(wal, offset)
let payload_len = match kind {
1U => page_size
2U => 0
_ => raise DbError::Corrupt
}
let frame_len = 16 + payload_len + 4
if offset + frame_len > wal.length() {
// 尾部半帧:崩溃只写了一半。等价的“忽略不完整尾部”。
break
}
let stored = read_u32(wal, offset + 16 + payload_len)
guard crc32(wal[offset:offset + 16 + payload_len]) == stored else {
raise DbError::Corrupt
}
let txn_id = read_u64_as_int(wal, offset + 4)
let page_id = read_u32(wal, offset + 12).reinterpret_as_int()
if kind == 1U {
guard page_id >= 0 else { raise DbError::Corrupt }
let pages = match pending.get(txn_id) {
Some(pages) => pages
None => Map([])
}
pages[page_id] = wal[offset + 16:offset + 16 + page_size]
pending[txn_id] = pages
} else {
guard page_id == 0 else { raise DbError::Corrupt }
if pending.get(txn_id) is Some(pages) {
for id, page in pages {
committed[id] = page.to_owned()
}
pending.remove(txn_id)
}
}
offset = offset + frame_len
}
committed
}