use byteorder::{BigEndian, ByteOrder};
use serde::Serialize;
use std::io::{Cursor, Read, Seek, SeekFrom};
use crate::IdbError;
trait ReadSeek: Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}
pub const LOG_BLOCK_SIZE: usize = 512;
pub const LOG_BLOCK_HDR_SIZE: usize = 14;
pub const LOG_BLOCK_TRL_SIZE: usize = 4;
pub const LOG_BLOCK_FLUSH_BIT_MASK: u32 = 0x80000000;
pub const LOG_BLOCK_CHECKSUM_OFFSET: usize = 508;
pub const LOG_FILE_HDR_BLOCKS: u64 = 4;
pub const LOG_HEADER_FORMAT: usize = 0;
pub const LOG_HEADER_GROUP_ID: usize = 0;
pub const LOG_HEADER_LOG_UUID: usize = 4;
pub const LOG_HEADER_START_LSN: usize = 8;
#[deprecated(note = "Use LOG_HEADER_START_LSN instead; file_no is not a separate field in 8.0.30+")]
pub const LOG_HEADER_FILE_NO: usize = 12;
pub const LOG_HEADER_CREATED_BY: usize = 16;
pub const LOG_HEADER_CREATED_BY_LEN: usize = 32;
pub const LOG_CHECKPOINT_NO: usize = 0;
pub const LOG_CHECKPOINT_LSN: usize = 8;
pub const LOG_CHECKPOINT_OFFSET: usize = 16;
pub const LOG_CHECKPOINT_BUF_SIZE: usize = 20;
pub const LOG_CHECKPOINT_ARCHIVED_LSN: usize = 24;
#[derive(Debug, Clone, Serialize)]
pub struct LogFileHeader {
pub format_version: u32,
pub start_lsn: u64,
#[serde(skip_serializing_if = "is_zero_u32")]
pub log_uuid: u32,
pub created_by: String,
}
fn is_zero_u32(v: &u32) -> bool {
*v == 0
}
impl LogFileHeader {
pub fn group_id(&self) -> u32 {
self.format_version
}
pub fn parse(block: &[u8]) -> Option<Self> {
if block.len() < LOG_BLOCK_SIZE {
return None;
}
let format_version = BigEndian::read_u32(&block[LOG_HEADER_FORMAT..]);
let (log_uuid, start_lsn) = if format_version >= 6 {
(
BigEndian::read_u32(&block[LOG_HEADER_LOG_UUID..]),
BigEndian::read_u64(&block[LOG_HEADER_START_LSN..]),
)
} else {
(0u32, BigEndian::read_u64(&block[4..]))
};
let created_bytes =
&block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + LOG_HEADER_CREATED_BY_LEN];
let created_by = created_bytes
.iter()
.take_while(|&&b| b != 0)
.map(|&b| b as char)
.collect::<String>();
Some(LogFileHeader {
format_version,
start_lsn,
log_uuid,
created_by,
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LogCheckpoint {
#[serde(skip_serializing_if = "is_zero_u64")]
pub number: u64,
pub lsn: u64,
#[serde(skip_serializing_if = "is_zero_u32")]
pub offset: u32,
#[serde(skip_serializing_if = "is_zero_u32")]
pub buf_size: u32,
#[serde(skip_serializing_if = "is_zero_u64")]
pub archived_lsn: u64,
}
fn is_zero_u64(v: &u64) -> bool {
*v == 0
}
impl LogCheckpoint {
pub fn parse(block: &[u8]) -> Option<Self> {
if block.len() < LOG_BLOCK_SIZE {
return None;
}
let number = BigEndian::read_u64(&block[LOG_CHECKPOINT_NO..]);
let lsn = BigEndian::read_u64(&block[LOG_CHECKPOINT_LSN..]);
let offset = BigEndian::read_u32(&block[LOG_CHECKPOINT_OFFSET..]);
let buf_size = BigEndian::read_u32(&block[LOG_CHECKPOINT_BUF_SIZE..]);
let archived_lsn = BigEndian::read_u64(&block[LOG_CHECKPOINT_ARCHIVED_LSN..]);
Some(LogCheckpoint {
number,
lsn,
offset,
buf_size,
archived_lsn,
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LogBlockHeader {
pub block_no: u32,
pub flush_flag: bool,
pub data_len: u16,
pub first_rec_group: u16,
pub epoch_no: u32,
}
impl LogBlockHeader {
pub fn parse(block: &[u8]) -> Option<Self> {
if block.len() < LOG_BLOCK_HDR_SIZE {
return None;
}
let raw_block_no = BigEndian::read_u32(&block[0..]);
let flush_flag = (raw_block_no & LOG_BLOCK_FLUSH_BIT_MASK) != 0;
let block_no = raw_block_no & !LOG_BLOCK_FLUSH_BIT_MASK;
let data_len = BigEndian::read_u16(&block[4..]);
let first_rec_group = BigEndian::read_u16(&block[6..]);
let epoch_no = BigEndian::read_u32(&block[8..]);
Some(LogBlockHeader {
block_no,
flush_flag,
data_len,
first_rec_group,
epoch_no,
})
}
pub fn checkpoint_no(&self) -> u32 {
self.epoch_no
}
pub fn has_data(&self) -> bool {
self.data_len as usize > LOG_BLOCK_HDR_SIZE
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LogBlockTrailer {
pub checksum: u32,
}
impl LogBlockTrailer {
pub fn parse(block: &[u8]) -> Option<Self> {
if block.len() < LOG_BLOCK_SIZE {
return None;
}
let checksum = BigEndian::read_u32(&block[LOG_BLOCK_CHECKSUM_OFFSET..]);
Some(LogBlockTrailer { checksum })
}
}
pub fn validate_log_block_checksum(block: &[u8]) -> bool {
if block.len() < LOG_BLOCK_SIZE {
return false;
}
let stored = BigEndian::read_u32(&block[LOG_BLOCK_CHECKSUM_OFFSET..]);
let calculated = crc32c::crc32c(&block[..LOG_BLOCK_CHECKSUM_OFFSET]);
stored == calculated
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum MlogRecordType {
Mlog1Byte,
Mlog2Bytes,
Mlog4Bytes,
Mlog8Bytes,
MlogRecInsert8027,
MlogRecClustDeleteMark8027,
MlogRecSecDeleteMark,
MlogRecUpdateInPlace8027,
MlogRecDelete8027,
MlogListEndDelete8027,
MlogListStartDelete8027,
MlogListEndCopyCreated8027,
MlogPageReorganize8027,
MlogPageCreate,
MlogUndoInsert,
MlogUndoEraseEnd,
MlogUndoInit,
MlogUndoHdrReuse,
MlogUndoHdrCreate,
MlogRecMinMark,
MlogIbufBitmapInit,
MlogLsn,
MlogInitFilePage,
MlogWriteString,
MlogMultiRecEnd,
MlogDummyRecord,
MlogFileCreate,
MlogFileRename,
MlogFileDelete,
MlogCompRecMinMark,
MlogCompPageCreate,
MlogCompRecInsert8027,
MlogCompRecClustDeleteMark8027,
MlogCompRecSecDeleteMark,
MlogCompRecUpdateInPlace8027,
MlogCompRecDelete8027,
MlogCompListEndDelete8027,
MlogCompListStartDelete8027,
MlogCompListEndCopyCreated8027,
MlogCompPageReorganize8027,
MlogZipWriteNodePtr,
MlogZipWriteBlobPtr,
MlogZipWriteHeader,
MlogZipPageCompress,
MlogZipPageCompressNoData8027,
MlogZipPageReorganize8027,
MlogPageCreateRTree,
MlogCompPageCreateRTree,
MlogInitFilePage2,
MlogIndexLoad,
MlogTableDynamicMeta,
MlogPageCreateSdi,
MlogCompPageCreateSdi,
MlogFileExtend,
MlogTest,
MlogRecInsert,
MlogRecClustDeleteMark,
MlogRecDelete,
MlogRecUpdateInPlace,
MlogListEndCopyCreated,
MlogPageReorganize,
MlogZipPageReorganize,
MlogZipPageCompressNoData,
MlogListEndDelete,
MlogListStartDelete,
Unknown(u8),
}
impl MlogRecordType {
pub fn from_u8(val: u8) -> Self {
match val {
1 => MlogRecordType::Mlog1Byte,
2 => MlogRecordType::Mlog2Bytes,
4 => MlogRecordType::Mlog4Bytes,
8 => MlogRecordType::Mlog8Bytes,
9 => MlogRecordType::MlogRecInsert8027,
10 => MlogRecordType::MlogRecClustDeleteMark8027,
11 => MlogRecordType::MlogRecSecDeleteMark,
13 => MlogRecordType::MlogRecUpdateInPlace8027,
14 => MlogRecordType::MlogRecDelete8027,
15 => MlogRecordType::MlogListEndDelete8027,
16 => MlogRecordType::MlogListStartDelete8027,
17 => MlogRecordType::MlogListEndCopyCreated8027,
18 => MlogRecordType::MlogPageReorganize8027,
19 => MlogRecordType::MlogPageCreate,
20 => MlogRecordType::MlogUndoInsert,
21 => MlogRecordType::MlogUndoEraseEnd,
22 => MlogRecordType::MlogUndoInit,
24 => MlogRecordType::MlogUndoHdrReuse,
25 => MlogRecordType::MlogUndoHdrCreate,
26 => MlogRecordType::MlogRecMinMark,
27 => MlogRecordType::MlogIbufBitmapInit,
28 => MlogRecordType::MlogLsn,
29 => MlogRecordType::MlogInitFilePage,
30 => MlogRecordType::MlogWriteString,
31 => MlogRecordType::MlogMultiRecEnd,
32 => MlogRecordType::MlogDummyRecord,
33 => MlogRecordType::MlogFileCreate,
34 => MlogRecordType::MlogFileRename,
35 => MlogRecordType::MlogFileDelete,
36 => MlogRecordType::MlogCompRecMinMark,
37 => MlogRecordType::MlogCompPageCreate,
38 => MlogRecordType::MlogCompRecInsert8027,
39 => MlogRecordType::MlogCompRecClustDeleteMark8027,
40 => MlogRecordType::MlogCompRecSecDeleteMark,
41 => MlogRecordType::MlogCompRecUpdateInPlace8027,
42 => MlogRecordType::MlogCompRecDelete8027,
43 => MlogRecordType::MlogCompListEndDelete8027,
44 => MlogRecordType::MlogCompListStartDelete8027,
45 => MlogRecordType::MlogCompListEndCopyCreated8027,
46 => MlogRecordType::MlogCompPageReorganize8027,
48 => MlogRecordType::MlogZipWriteNodePtr,
49 => MlogRecordType::MlogZipWriteBlobPtr,
50 => MlogRecordType::MlogZipWriteHeader,
51 => MlogRecordType::MlogZipPageCompress,
52 => MlogRecordType::MlogZipPageCompressNoData8027,
53 => MlogRecordType::MlogZipPageReorganize8027,
57 => MlogRecordType::MlogPageCreateRTree,
58 => MlogRecordType::MlogCompPageCreateRTree,
59 => MlogRecordType::MlogInitFilePage2,
61 => MlogRecordType::MlogIndexLoad,
62 => MlogRecordType::MlogTableDynamicMeta,
63 => MlogRecordType::MlogPageCreateSdi,
64 => MlogRecordType::MlogCompPageCreateSdi,
65 => MlogRecordType::MlogFileExtend,
66 => MlogRecordType::MlogTest,
67 => MlogRecordType::MlogRecInsert,
68 => MlogRecordType::MlogRecClustDeleteMark,
69 => MlogRecordType::MlogRecDelete,
70 => MlogRecordType::MlogRecUpdateInPlace,
71 => MlogRecordType::MlogListEndCopyCreated,
72 => MlogRecordType::MlogPageReorganize,
73 => MlogRecordType::MlogZipPageReorganize,
74 => MlogRecordType::MlogZipPageCompressNoData,
75 => MlogRecordType::MlogListEndDelete,
76 => MlogRecordType::MlogListStartDelete,
v => MlogRecordType::Unknown(v),
}
}
pub fn name(&self) -> &str {
match self {
MlogRecordType::Mlog1Byte => "MLOG_1BYTE",
MlogRecordType::Mlog2Bytes => "MLOG_2BYTES",
MlogRecordType::Mlog4Bytes => "MLOG_4BYTES",
MlogRecordType::Mlog8Bytes => "MLOG_8BYTES",
MlogRecordType::MlogRecInsert8027 => "MLOG_REC_INSERT_8027",
MlogRecordType::MlogRecClustDeleteMark8027 => "MLOG_REC_CLUST_DELETE_MARK_8027",
MlogRecordType::MlogRecSecDeleteMark => "MLOG_REC_SEC_DELETE_MARK",
MlogRecordType::MlogRecUpdateInPlace8027 => "MLOG_REC_UPDATE_IN_PLACE_8027",
MlogRecordType::MlogRecDelete8027 => "MLOG_REC_DELETE_8027",
MlogRecordType::MlogListEndDelete8027 => "MLOG_LIST_END_DELETE_8027",
MlogRecordType::MlogListStartDelete8027 => "MLOG_LIST_START_DELETE_8027",
MlogRecordType::MlogListEndCopyCreated8027 => "MLOG_LIST_END_COPY_CREATED_8027",
MlogRecordType::MlogPageReorganize8027 => "MLOG_PAGE_REORGANIZE_8027",
MlogRecordType::MlogPageCreate => "MLOG_PAGE_CREATE",
MlogRecordType::MlogUndoInsert => "MLOG_UNDO_INSERT",
MlogRecordType::MlogUndoEraseEnd => "MLOG_UNDO_ERASE_END",
MlogRecordType::MlogUndoInit => "MLOG_UNDO_INIT",
MlogRecordType::MlogUndoHdrReuse => "MLOG_UNDO_HDR_REUSE",
MlogRecordType::MlogUndoHdrCreate => "MLOG_UNDO_HDR_CREATE",
MlogRecordType::MlogRecMinMark => "MLOG_REC_MIN_MARK",
MlogRecordType::MlogIbufBitmapInit => "MLOG_IBUF_BITMAP_INIT",
MlogRecordType::MlogLsn => "MLOG_LSN",
MlogRecordType::MlogInitFilePage => "MLOG_INIT_FILE_PAGE",
MlogRecordType::MlogWriteString => "MLOG_WRITE_STRING",
MlogRecordType::MlogMultiRecEnd => "MLOG_MULTI_REC_END",
MlogRecordType::MlogDummyRecord => "MLOG_DUMMY_RECORD",
MlogRecordType::MlogFileCreate => "MLOG_FILE_CREATE",
MlogRecordType::MlogFileRename => "MLOG_FILE_RENAME",
MlogRecordType::MlogFileDelete => "MLOG_FILE_DELETE",
MlogRecordType::MlogCompRecMinMark => "MLOG_COMP_REC_MIN_MARK",
MlogRecordType::MlogCompPageCreate => "MLOG_COMP_PAGE_CREATE",
MlogRecordType::MlogCompRecInsert8027 => "MLOG_COMP_REC_INSERT_8027",
MlogRecordType::MlogCompRecClustDeleteMark8027 => {
"MLOG_COMP_REC_CLUST_DELETE_MARK_8027"
}
MlogRecordType::MlogCompRecSecDeleteMark => "MLOG_COMP_REC_SEC_DELETE_MARK",
MlogRecordType::MlogCompRecUpdateInPlace8027 => "MLOG_COMP_REC_UPDATE_IN_PLACE_8027",
MlogRecordType::MlogCompRecDelete8027 => "MLOG_COMP_REC_DELETE_8027",
MlogRecordType::MlogCompListEndDelete8027 => "MLOG_COMP_LIST_END_DELETE_8027",
MlogRecordType::MlogCompListStartDelete8027 => "MLOG_COMP_LIST_START_DELETE_8027",
MlogRecordType::MlogCompListEndCopyCreated8027 => {
"MLOG_COMP_LIST_END_COPY_CREATED_8027"
}
MlogRecordType::MlogCompPageReorganize8027 => "MLOG_COMP_PAGE_REORGANIZE_8027",
MlogRecordType::MlogZipWriteNodePtr => "MLOG_ZIP_WRITE_NODE_PTR",
MlogRecordType::MlogZipWriteBlobPtr => "MLOG_ZIP_WRITE_BLOB_PTR",
MlogRecordType::MlogZipWriteHeader => "MLOG_ZIP_WRITE_HEADER",
MlogRecordType::MlogZipPageCompress => "MLOG_ZIP_PAGE_COMPRESS",
MlogRecordType::MlogZipPageCompressNoData8027 => "MLOG_ZIP_PAGE_COMPRESS_NO_DATA_8027",
MlogRecordType::MlogZipPageReorganize8027 => "MLOG_ZIP_PAGE_REORGANIZE_8027",
MlogRecordType::MlogPageCreateRTree => "MLOG_PAGE_CREATE_RTREE",
MlogRecordType::MlogCompPageCreateRTree => "MLOG_COMP_PAGE_CREATE_RTREE",
MlogRecordType::MlogInitFilePage2 => "MLOG_INIT_FILE_PAGE2",
MlogRecordType::MlogIndexLoad => "MLOG_INDEX_LOAD",
MlogRecordType::MlogTableDynamicMeta => "MLOG_TABLE_DYNAMIC_META",
MlogRecordType::MlogPageCreateSdi => "MLOG_PAGE_CREATE_SDI",
MlogRecordType::MlogCompPageCreateSdi => "MLOG_COMP_PAGE_CREATE_SDI",
MlogRecordType::MlogFileExtend => "MLOG_FILE_EXTEND",
MlogRecordType::MlogTest => "MLOG_TEST",
MlogRecordType::MlogRecInsert => "MLOG_REC_INSERT",
MlogRecordType::MlogRecClustDeleteMark => "MLOG_REC_CLUST_DELETE_MARK",
MlogRecordType::MlogRecDelete => "MLOG_REC_DELETE",
MlogRecordType::MlogRecUpdateInPlace => "MLOG_REC_UPDATE_IN_PLACE",
MlogRecordType::MlogListEndCopyCreated => "MLOG_LIST_END_COPY_CREATED",
MlogRecordType::MlogPageReorganize => "MLOG_PAGE_REORGANIZE",
MlogRecordType::MlogZipPageReorganize => "MLOG_ZIP_PAGE_REORGANIZE",
MlogRecordType::MlogZipPageCompressNoData => "MLOG_ZIP_PAGE_COMPRESS_NO_DATA",
MlogRecordType::MlogListEndDelete => "MLOG_LIST_END_DELETE",
MlogRecordType::MlogListStartDelete => "MLOG_LIST_START_DELETE",
MlogRecordType::Unknown(_) => "UNKNOWN",
}
}
}
impl std::fmt::Display for MlogRecordType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MlogRecordType::Unknown(v) => write!(f, "UNKNOWN({})", v),
_ => write!(f, "{}", self.name()),
}
}
}
pub struct LogFile {
reader: Box<dyn ReadSeek>,
file_size: u64,
}
impl LogFile {
#[cfg(not(target_arch = "wasm32"))]
pub fn open(path: &str) -> Result<Self, IdbError> {
let file = std::fs::File::open(path)
.map_err(|e| IdbError::Io(format!("Cannot open {}: {}", path, e)))?;
let file_size = file
.metadata()
.map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", path, e)))?
.len();
Self::init(Box::new(file), file_size)
}
pub fn from_bytes(data: Vec<u8>) -> Result<Self, IdbError> {
let file_size = data.len() as u64;
Self::init(Box::new(Cursor::new(data)), file_size)
}
fn init(reader: Box<dyn ReadSeek>, file_size: u64) -> Result<Self, IdbError> {
if file_size < (LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE) as u64 {
return Err(IdbError::Parse(format!(
"File is too small for a redo log ({} bytes, minimum {})",
file_size,
LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE
)));
}
Ok(LogFile { reader, file_size })
}
pub fn block_count(&self) -> u64 {
self.file_size / LOG_BLOCK_SIZE as u64
}
pub fn data_block_count(&self) -> u64 {
self.block_count().saturating_sub(LOG_FILE_HDR_BLOCKS)
}
pub fn read_block(&mut self, block_no: u64) -> Result<Vec<u8>, IdbError> {
let offset = block_no * LOG_BLOCK_SIZE as u64;
if offset + LOG_BLOCK_SIZE as u64 > self.file_size {
return Err(IdbError::Io(format!(
"Block {} is beyond end of file (offset {}, file size {})",
block_no, offset, self.file_size
)));
}
self.reader
.seek(SeekFrom::Start(offset))
.map_err(|e| IdbError::Io(format!("Seek error: {}", e)))?;
let mut buf = vec![0u8; LOG_BLOCK_SIZE];
self.reader
.read_exact(&mut buf)
.map_err(|e| IdbError::Io(format!("Read error at block {}: {}", block_no, e)))?;
Ok(buf)
}
pub fn read_header(&mut self) -> Result<LogFileHeader, IdbError> {
let block = self.read_block(0)?;
LogFileHeader::parse(&block)
.ok_or_else(|| IdbError::Parse("Failed to parse log file header (block 0)".to_string()))
}
pub fn read_checkpoint(&mut self, slot: u8) -> Result<LogCheckpoint, IdbError> {
let block_no = match slot {
0 => 1,
1 => 3,
_ => {
return Err(IdbError::Argument(format!(
"Invalid checkpoint slot {} (must be 0 or 1)",
slot
)))
}
};
let block = self.read_block(block_no)?;
LogCheckpoint::parse(&block).ok_or_else(|| {
IdbError::Parse(format!("Failed to parse checkpoint at block {}", block_no))
})
}
pub fn file_size(&self) -> u64 {
self.file_size
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_block() -> Vec<u8> {
vec![0u8; LOG_BLOCK_SIZE]
}
#[test]
fn test_log_block_header_parse() {
let mut block = make_block();
BigEndian::write_u32(&mut block[0..], 42);
BigEndian::write_u16(&mut block[4..], 200);
BigEndian::write_u16(&mut block[6..], 50);
BigEndian::write_u32(&mut block[8..], 7);
let hdr = LogBlockHeader::parse(&block).unwrap();
assert_eq!(hdr.block_no, 42);
assert!(!hdr.flush_flag);
assert_eq!(hdr.data_len, 200);
assert_eq!(hdr.first_rec_group, 50);
assert_eq!(hdr.epoch_no, 7);
assert_eq!(hdr.checkpoint_no(), 7);
assert!(hdr.has_data());
}
#[test]
fn test_log_block_flush_bit() {
let mut block = make_block();
BigEndian::write_u32(&mut block[0..], 0x80000064);
BigEndian::write_u16(&mut block[4..], 14);
let hdr = LogBlockHeader::parse(&block).unwrap();
assert!(hdr.flush_flag);
assert_eq!(hdr.block_no, 100);
assert!(!hdr.has_data());
}
#[test]
fn test_log_block_header_empty() {
let block = make_block();
let hdr = LogBlockHeader::parse(&block).unwrap();
assert_eq!(hdr.block_no, 0);
assert!(!hdr.flush_flag);
assert_eq!(hdr.data_len, 0);
assert_eq!(hdr.first_rec_group, 0);
assert_eq!(hdr.epoch_no, 0);
assert!(!hdr.has_data());
}
#[test]
fn test_log_block_header_too_small() {
let block = vec![0u8; 10]; assert!(LogBlockHeader::parse(&block).is_none());
}
#[test]
fn test_log_block_trailer_parse() {
let mut block = make_block();
BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xCAFEBABE);
let trailer = LogBlockTrailer::parse(&block).unwrap();
assert_eq!(trailer.checksum, 0xCAFEBABE);
}
#[test]
fn test_log_file_header_format_v6() {
let mut block = make_block();
BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 6);
BigEndian::write_u32(&mut block[LOG_HEADER_LOG_UUID..], 0xABCD1234);
BigEndian::write_u64(&mut block[LOG_HEADER_START_LSN..], 0x00000000001A2B3C);
let creator = b"MySQL 9.0.1";
block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
.copy_from_slice(creator);
let hdr = LogFileHeader::parse(&block).unwrap();
assert_eq!(hdr.format_version, 6);
assert_eq!(hdr.group_id(), 6);
assert_eq!(hdr.start_lsn, 0x1A2B3C);
assert_eq!(hdr.log_uuid, 0xABCD1234);
assert_eq!(hdr.created_by, "MySQL 9.0.1");
}
#[test]
fn test_log_file_header_pre_8030() {
let mut block = make_block();
BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 1);
BigEndian::write_u64(&mut block[4..], 0x00000000001A2B3C); let creator = b"MySQL 5.7.44";
block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
.copy_from_slice(creator);
let hdr = LogFileHeader::parse(&block).unwrap();
assert_eq!(hdr.format_version, 1);
assert_eq!(hdr.start_lsn, 0x1A2B3C);
assert_eq!(hdr.log_uuid, 0); assert_eq!(hdr.created_by, "MySQL 5.7.44");
}
#[test]
fn test_log_file_header_format_v5() {
let mut block = make_block();
BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 5);
BigEndian::write_u64(&mut block[4..], 0x00000000DEADBEEF);
let creator = b"MySQL 8.0.28";
block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
.copy_from_slice(creator);
let hdr = LogFileHeader::parse(&block).unwrap();
assert_eq!(hdr.format_version, 5);
assert_eq!(hdr.start_lsn, 0xDEADBEEF);
assert_eq!(hdr.log_uuid, 0);
assert_eq!(hdr.created_by, "MySQL 8.0.28");
}
#[test]
fn test_log_file_header_empty_created_by() {
let block = make_block();
let hdr = LogFileHeader::parse(&block).unwrap();
assert_eq!(hdr.created_by, "");
}
#[test]
fn test_log_checkpoint_parse() {
let mut block = make_block();
BigEndian::write_u64(&mut block[LOG_CHECKPOINT_NO..], 99);
BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 0x00000000DEADBEEF);
BigEndian::write_u32(&mut block[LOG_CHECKPOINT_OFFSET..], 2048);
BigEndian::write_u32(&mut block[LOG_CHECKPOINT_BUF_SIZE..], 65536);
BigEndian::write_u64(
&mut block[LOG_CHECKPOINT_ARCHIVED_LSN..],
0x00000000CAFEBABE,
);
let cp = LogCheckpoint::parse(&block).unwrap();
assert_eq!(cp.number, 99);
assert_eq!(cp.lsn, 0xDEADBEEF);
assert_eq!(cp.offset, 2048);
assert_eq!(cp.buf_size, 65536);
assert_eq!(cp.archived_lsn, 0xCAFEBABE);
}
#[test]
fn test_log_checkpoint_format_v6() {
let mut block = make_block();
BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 32193931);
let cp = LogCheckpoint::parse(&block).unwrap();
assert_eq!(cp.lsn, 32193931);
assert_eq!(cp.number, 0);
assert_eq!(cp.offset, 0);
assert_eq!(cp.buf_size, 0);
assert_eq!(cp.archived_lsn, 0);
}
#[test]
fn test_mlog_record_type_basic_writes() {
assert_eq!(MlogRecordType::from_u8(1), MlogRecordType::Mlog1Byte);
assert_eq!(MlogRecordType::from_u8(2), MlogRecordType::Mlog2Bytes);
assert_eq!(MlogRecordType::from_u8(4), MlogRecordType::Mlog4Bytes);
assert_eq!(MlogRecordType::from_u8(8), MlogRecordType::Mlog8Bytes);
}
#[test]
fn test_mlog_record_type_pre_8028() {
assert_eq!(
MlogRecordType::from_u8(9),
MlogRecordType::MlogRecInsert8027
);
assert_eq!(
MlogRecordType::from_u8(10),
MlogRecordType::MlogRecClustDeleteMark8027
);
assert_eq!(
MlogRecordType::from_u8(18),
MlogRecordType::MlogPageReorganize8027
);
assert_eq!(
MlogRecordType::from_u8(38),
MlogRecordType::MlogCompRecInsert8027
);
assert_eq!(
MlogRecordType::from_u8(52),
MlogRecordType::MlogZipPageCompressNoData8027
);
assert_eq!(
MlogRecordType::from_u8(53),
MlogRecordType::MlogZipPageReorganize8027
);
}
#[test]
fn test_mlog_record_type_corrected_mappings() {
assert_eq!(
MlogRecordType::from_u8(25),
MlogRecordType::MlogUndoHdrCreate
);
assert_eq!(MlogRecordType::from_u8(26), MlogRecordType::MlogRecMinMark);
assert_eq!(
MlogRecordType::from_u8(27),
MlogRecordType::MlogIbufBitmapInit
);
assert_eq!(MlogRecordType::from_u8(28), MlogRecordType::MlogLsn);
assert_eq!(
MlogRecordType::from_u8(29),
MlogRecordType::MlogInitFilePage
);
assert_eq!(MlogRecordType::from_u8(30), MlogRecordType::MlogWriteString);
assert_eq!(MlogRecordType::from_u8(31), MlogRecordType::MlogMultiRecEnd);
assert_eq!(MlogRecordType::from_u8(32), MlogRecordType::MlogDummyRecord);
assert_eq!(MlogRecordType::from_u8(33), MlogRecordType::MlogFileCreate);
assert_eq!(MlogRecordType::from_u8(34), MlogRecordType::MlogFileRename);
assert_eq!(MlogRecordType::from_u8(35), MlogRecordType::MlogFileDelete);
assert_eq!(
MlogRecordType::from_u8(36),
MlogRecordType::MlogCompRecMinMark
);
assert_eq!(
MlogRecordType::from_u8(37),
MlogRecordType::MlogCompPageCreate
);
}
#[test]
fn test_mlog_record_type_post_8028() {
assert_eq!(MlogRecordType::from_u8(67), MlogRecordType::MlogRecInsert);
assert_eq!(
MlogRecordType::from_u8(68),
MlogRecordType::MlogRecClustDeleteMark
);
assert_eq!(MlogRecordType::from_u8(69), MlogRecordType::MlogRecDelete);
assert_eq!(
MlogRecordType::from_u8(70),
MlogRecordType::MlogRecUpdateInPlace
);
assert_eq!(
MlogRecordType::from_u8(71),
MlogRecordType::MlogListEndCopyCreated
);
assert_eq!(
MlogRecordType::from_u8(72),
MlogRecordType::MlogPageReorganize
);
assert_eq!(
MlogRecordType::from_u8(73),
MlogRecordType::MlogZipPageReorganize
);
assert_eq!(
MlogRecordType::from_u8(74),
MlogRecordType::MlogZipPageCompressNoData
);
assert_eq!(
MlogRecordType::from_u8(75),
MlogRecordType::MlogListEndDelete
);
assert_eq!(
MlogRecordType::from_u8(76),
MlogRecordType::MlogListStartDelete
);
}
#[test]
fn test_mlog_record_type_extended() {
assert_eq!(
MlogRecordType::from_u8(57),
MlogRecordType::MlogPageCreateRTree
);
assert_eq!(
MlogRecordType::from_u8(59),
MlogRecordType::MlogInitFilePage2
);
assert_eq!(MlogRecordType::from_u8(61), MlogRecordType::MlogIndexLoad);
assert_eq!(
MlogRecordType::from_u8(62),
MlogRecordType::MlogTableDynamicMeta
);
assert_eq!(
MlogRecordType::from_u8(63),
MlogRecordType::MlogPageCreateSdi
);
assert_eq!(
MlogRecordType::from_u8(64),
MlogRecordType::MlogCompPageCreateSdi
);
assert_eq!(MlogRecordType::from_u8(65), MlogRecordType::MlogFileExtend);
assert_eq!(MlogRecordType::from_u8(66), MlogRecordType::MlogTest);
}
#[test]
fn test_mlog_record_type_unknown() {
assert_eq!(MlogRecordType::from_u8(0), MlogRecordType::Unknown(0));
assert_eq!(MlogRecordType::from_u8(255), MlogRecordType::Unknown(255));
assert_eq!(MlogRecordType::from_u8(100), MlogRecordType::Unknown(100));
assert_eq!(MlogRecordType::from_u8(3), MlogRecordType::Unknown(3));
assert_eq!(MlogRecordType::from_u8(12), MlogRecordType::Unknown(12));
assert_eq!(MlogRecordType::from_u8(23), MlogRecordType::Unknown(23));
assert_eq!(MlogRecordType::from_u8(47), MlogRecordType::Unknown(47));
assert_eq!(MlogRecordType::from_u8(60), MlogRecordType::Unknown(60));
assert_eq!(MlogRecordType::from_u8(77), MlogRecordType::Unknown(77));
}
#[test]
fn test_mlog_record_type_name() {
assert_eq!(MlogRecordType::Mlog1Byte.name(), "MLOG_1BYTE");
assert_eq!(MlogRecordType::MlogRecInsert.name(), "MLOG_REC_INSERT");
assert_eq!(
MlogRecordType::MlogRecInsert8027.name(),
"MLOG_REC_INSERT_8027"
);
assert_eq!(MlogRecordType::MlogFileExtend.name(), "MLOG_FILE_EXTEND");
assert_eq!(MlogRecordType::MlogLsn.name(), "MLOG_LSN");
assert_eq!(MlogRecordType::Unknown(99).name(), "UNKNOWN");
}
#[test]
fn test_mlog_record_type_display() {
assert_eq!(format!("{}", MlogRecordType::Mlog1Byte), "MLOG_1BYTE");
assert_eq!(format!("{}", MlogRecordType::Unknown(99)), "UNKNOWN(99)");
assert_eq!(
format!("{}", MlogRecordType::MlogListEndDelete),
"MLOG_LIST_END_DELETE"
);
}
#[test]
fn test_log_block_checksum_validation() {
let mut block = make_block();
BigEndian::write_u32(&mut block[0..], 5); BigEndian::write_u16(&mut block[4..], 100); BigEndian::write_u16(&mut block[6..], 12); block[12] = 0xAB;
let crc = crc32c::crc32c(&block[..LOG_BLOCK_CHECKSUM_OFFSET]);
BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], crc);
assert!(validate_log_block_checksum(&block));
}
#[test]
fn test_log_from_bytes_empty() {
let result = LogFile::from_bytes(vec![]);
match result {
Err(e) => assert!(
e.to_string().contains("too small"),
"Expected 'too small' in: {e}"
),
Ok(_) => panic!("Expected error for empty input"),
}
}
#[test]
fn test_log_from_bytes_too_small() {
let result = LogFile::from_bytes(vec![0u8; 100]);
match result {
Err(e) => assert!(
e.to_string().contains("too small"),
"Expected 'too small' in: {e}"
),
Ok(_) => panic!("Expected error for 100-byte input"),
}
}
#[test]
fn test_log_block_checksum_invalid() {
let mut block = make_block();
BigEndian::write_u32(&mut block[0..], 5);
BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xDEADDEAD);
assert!(!validate_log_block_checksum(&block));
}
}