1use byteorder::{BigEndian, ByteOrder};
29use serde::Serialize;
30use std::io::{Cursor, Read, Seek, SeekFrom};
31
32use crate::IdbError;
33
34trait ReadSeek: Read + Seek {}
36impl<T: Read + Seek> ReadSeek for T {}
37
38pub const LOG_BLOCK_SIZE: usize = 512;
40pub const LOG_BLOCK_HDR_SIZE: usize = 14;
47pub const LOG_BLOCK_TRL_SIZE: usize = 4;
49pub const LOG_BLOCK_FLUSH_BIT_MASK: u32 = 0x80000000;
51pub const LOG_BLOCK_CHECKSUM_OFFSET: usize = 508;
53pub const LOG_FILE_HDR_BLOCKS: u64 = 4;
55
56pub const LOG_HEADER_FORMAT: usize = 0;
61pub const LOG_HEADER_GROUP_ID: usize = 0;
66pub const LOG_HEADER_LOG_UUID: usize = 4;
71pub const LOG_HEADER_START_LSN: usize = 8;
73#[deprecated(note = "Use LOG_HEADER_START_LSN instead; file_no is not a separate field in 8.0.30+")]
78pub const LOG_HEADER_FILE_NO: usize = 12;
79pub const LOG_HEADER_CREATED_BY: usize = 16;
81pub const LOG_HEADER_CREATED_BY_LEN: usize = 32;
83
84pub const LOG_CHECKPOINT_NO: usize = 0;
89pub const LOG_CHECKPOINT_LSN: usize = 8;
91pub const LOG_CHECKPOINT_OFFSET: usize = 16;
95pub const LOG_CHECKPOINT_BUF_SIZE: usize = 20;
99pub const LOG_CHECKPOINT_ARCHIVED_LSN: usize = 24;
103
104#[derive(Debug, Clone, Serialize)]
119pub struct LogFileHeader {
120 pub format_version: u32,
126 pub start_lsn: u64,
128 #[serde(skip_serializing_if = "is_zero_u32")]
132 pub log_uuid: u32,
133 pub created_by: String,
135}
136
137fn is_zero_u32(v: &u32) -> bool {
138 *v == 0
139}
140
141impl LogFileHeader {
142 pub fn group_id(&self) -> u32 {
147 self.format_version
148 }
149
150 pub fn parse(block: &[u8]) -> Option<Self> {
181 if block.len() < LOG_BLOCK_SIZE {
182 return None;
183 }
184
185 let format_version = BigEndian::read_u32(&block[LOG_HEADER_FORMAT..]);
186
187 let (log_uuid, start_lsn) = if format_version >= 6 {
188 (
190 BigEndian::read_u32(&block[LOG_HEADER_LOG_UUID..]),
191 BigEndian::read_u64(&block[LOG_HEADER_START_LSN..]),
192 )
193 } else {
194 (0u32, BigEndian::read_u64(&block[4..]))
196 };
197
198 let created_bytes =
199 &block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + LOG_HEADER_CREATED_BY_LEN];
200 let created_by = created_bytes
201 .iter()
202 .take_while(|&&b| b != 0)
203 .map(|&b| b as char)
204 .collect::<String>();
205
206 Some(LogFileHeader {
207 format_version,
208 start_lsn,
209 log_uuid,
210 created_by,
211 })
212 }
213}
214
215#[derive(Debug, Clone, Serialize)]
221pub struct LogCheckpoint {
222 #[serde(skip_serializing_if = "is_zero_u64")]
224 pub number: u64,
225 pub lsn: u64,
227 #[serde(skip_serializing_if = "is_zero_u32")]
229 pub offset: u32,
230 #[serde(skip_serializing_if = "is_zero_u32")]
232 pub buf_size: u32,
233 #[serde(skip_serializing_if = "is_zero_u64")]
235 pub archived_lsn: u64,
236}
237
238fn is_zero_u64(v: &u64) -> bool {
239 *v == 0
240}
241
242impl LogCheckpoint {
243 pub fn parse(block: &[u8]) -> Option<Self> {
272 if block.len() < LOG_BLOCK_SIZE {
273 return None;
274 }
275
276 let number = BigEndian::read_u64(&block[LOG_CHECKPOINT_NO..]);
277 let lsn = BigEndian::read_u64(&block[LOG_CHECKPOINT_LSN..]);
278 let offset = BigEndian::read_u32(&block[LOG_CHECKPOINT_OFFSET..]);
279 let buf_size = BigEndian::read_u32(&block[LOG_CHECKPOINT_BUF_SIZE..]);
280 let archived_lsn = BigEndian::read_u64(&block[LOG_CHECKPOINT_ARCHIVED_LSN..]);
281
282 Some(LogCheckpoint {
283 number,
284 lsn,
285 offset,
286 buf_size,
287 archived_lsn,
288 })
289 }
290}
291
292#[derive(Debug, Clone, Serialize)]
307pub struct LogBlockHeader {
308 pub block_no: u32,
310 pub flush_flag: bool,
312 pub data_len: u16,
314 pub first_rec_group: u16,
316 pub epoch_no: u32,
322}
323
324impl LogBlockHeader {
325 pub fn parse(block: &[u8]) -> Option<Self> {
348 if block.len() < LOG_BLOCK_HDR_SIZE {
349 return None;
350 }
351
352 let raw_block_no = BigEndian::read_u32(&block[0..]);
353 let flush_flag = (raw_block_no & LOG_BLOCK_FLUSH_BIT_MASK) != 0;
354 let block_no = raw_block_no & !LOG_BLOCK_FLUSH_BIT_MASK;
355
356 let data_len = BigEndian::read_u16(&block[4..]);
357 let first_rec_group = BigEndian::read_u16(&block[6..]);
358 let epoch_no = BigEndian::read_u32(&block[8..]);
359
360 Some(LogBlockHeader {
361 block_no,
362 flush_flag,
363 data_len,
364 first_rec_group,
365 epoch_no,
366 })
367 }
368
369 pub fn checkpoint_no(&self) -> u32 {
374 self.epoch_no
375 }
376
377 pub fn has_data(&self) -> bool {
379 self.data_len as usize > LOG_BLOCK_HDR_SIZE
380 }
381}
382
383#[derive(Debug, Clone, Serialize)]
385pub struct LogBlockTrailer {
386 pub checksum: u32,
388}
389
390impl LogBlockTrailer {
391 pub fn parse(block: &[u8]) -> Option<Self> {
393 if block.len() < LOG_BLOCK_SIZE {
394 return None;
395 }
396
397 let checksum = BigEndian::read_u32(&block[LOG_BLOCK_CHECKSUM_OFFSET..]);
398
399 Some(LogBlockTrailer { checksum })
400 }
401}
402
403pub fn validate_log_block_checksum(block: &[u8]) -> bool {
407 if block.len() < LOG_BLOCK_SIZE {
408 return false;
409 }
410 let stored = BigEndian::read_u32(&block[LOG_BLOCK_CHECKSUM_OFFSET..]);
411 let calculated = crc32c::crc32c(&block[..LOG_BLOCK_CHECKSUM_OFFSET]);
412 stored == calculated
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
425pub enum MlogRecordType {
426 Mlog1Byte,
429 Mlog2Bytes,
431 Mlog4Bytes,
433 Mlog8Bytes,
435
436 MlogRecInsert8027,
439 MlogRecClustDeleteMark8027,
441 MlogRecSecDeleteMark,
443 MlogRecUpdateInPlace8027,
445 MlogRecDelete8027,
447 MlogListEndDelete8027,
449 MlogListStartDelete8027,
451 MlogListEndCopyCreated8027,
453 MlogPageReorganize8027,
455
456 MlogPageCreate,
459 MlogUndoInsert,
461 MlogUndoEraseEnd,
463 MlogUndoInit,
465 MlogUndoHdrReuse,
467
468 MlogUndoHdrCreate,
471 MlogRecMinMark,
473 MlogIbufBitmapInit,
475 MlogLsn,
477 MlogInitFilePage,
479 MlogWriteString,
481 MlogMultiRecEnd,
483 MlogDummyRecord,
485 MlogFileCreate,
487 MlogFileRename,
489 MlogFileDelete,
491
492 MlogCompRecMinMark,
495 MlogCompPageCreate,
497 MlogCompRecInsert8027,
499 MlogCompRecClustDeleteMark8027,
501 MlogCompRecSecDeleteMark,
503 MlogCompRecUpdateInPlace8027,
505 MlogCompRecDelete8027,
507 MlogCompListEndDelete8027,
509 MlogCompListStartDelete8027,
511 MlogCompListEndCopyCreated8027,
513 MlogCompPageReorganize8027,
515
516 MlogZipWriteNodePtr,
519 MlogZipWriteBlobPtr,
521 MlogZipWriteHeader,
523 MlogZipPageCompress,
525 MlogZipPageCompressNoData8027,
527 MlogZipPageReorganize8027,
529
530 MlogPageCreateRTree,
533 MlogCompPageCreateRTree,
535 MlogInitFilePage2,
537 MlogIndexLoad,
539 MlogTableDynamicMeta,
541 MlogPageCreateSdi,
543 MlogCompPageCreateSdi,
545 MlogFileExtend,
547 MlogTest,
549
550 MlogRecInsert,
553 MlogRecClustDeleteMark,
555 MlogRecDelete,
557 MlogRecUpdateInPlace,
559 MlogListEndCopyCreated,
561 MlogPageReorganize,
563 MlogZipPageReorganize,
565 MlogZipPageCompressNoData,
567 MlogListEndDelete,
569 MlogListStartDelete,
571
572 Unknown(u8),
574}
575
576impl MlogRecordType {
577 pub fn from_u8(val: u8) -> Self {
605 match val {
606 1 => MlogRecordType::Mlog1Byte,
607 2 => MlogRecordType::Mlog2Bytes,
608 4 => MlogRecordType::Mlog4Bytes,
609 8 => MlogRecordType::Mlog8Bytes,
610 9 => MlogRecordType::MlogRecInsert8027,
611 10 => MlogRecordType::MlogRecClustDeleteMark8027,
612 11 => MlogRecordType::MlogRecSecDeleteMark,
613 13 => MlogRecordType::MlogRecUpdateInPlace8027,
614 14 => MlogRecordType::MlogRecDelete8027,
615 15 => MlogRecordType::MlogListEndDelete8027,
616 16 => MlogRecordType::MlogListStartDelete8027,
617 17 => MlogRecordType::MlogListEndCopyCreated8027,
618 18 => MlogRecordType::MlogPageReorganize8027,
619 19 => MlogRecordType::MlogPageCreate,
620 20 => MlogRecordType::MlogUndoInsert,
621 21 => MlogRecordType::MlogUndoEraseEnd,
622 22 => MlogRecordType::MlogUndoInit,
623 24 => MlogRecordType::MlogUndoHdrReuse,
624 25 => MlogRecordType::MlogUndoHdrCreate,
625 26 => MlogRecordType::MlogRecMinMark,
626 27 => MlogRecordType::MlogIbufBitmapInit,
627 28 => MlogRecordType::MlogLsn,
628 29 => MlogRecordType::MlogInitFilePage,
629 30 => MlogRecordType::MlogWriteString,
630 31 => MlogRecordType::MlogMultiRecEnd,
631 32 => MlogRecordType::MlogDummyRecord,
632 33 => MlogRecordType::MlogFileCreate,
633 34 => MlogRecordType::MlogFileRename,
634 35 => MlogRecordType::MlogFileDelete,
635 36 => MlogRecordType::MlogCompRecMinMark,
636 37 => MlogRecordType::MlogCompPageCreate,
637 38 => MlogRecordType::MlogCompRecInsert8027,
638 39 => MlogRecordType::MlogCompRecClustDeleteMark8027,
639 40 => MlogRecordType::MlogCompRecSecDeleteMark,
640 41 => MlogRecordType::MlogCompRecUpdateInPlace8027,
641 42 => MlogRecordType::MlogCompRecDelete8027,
642 43 => MlogRecordType::MlogCompListEndDelete8027,
643 44 => MlogRecordType::MlogCompListStartDelete8027,
644 45 => MlogRecordType::MlogCompListEndCopyCreated8027,
645 46 => MlogRecordType::MlogCompPageReorganize8027,
646 48 => MlogRecordType::MlogZipWriteNodePtr,
647 49 => MlogRecordType::MlogZipWriteBlobPtr,
648 50 => MlogRecordType::MlogZipWriteHeader,
649 51 => MlogRecordType::MlogZipPageCompress,
650 52 => MlogRecordType::MlogZipPageCompressNoData8027,
651 53 => MlogRecordType::MlogZipPageReorganize8027,
652 57 => MlogRecordType::MlogPageCreateRTree,
653 58 => MlogRecordType::MlogCompPageCreateRTree,
654 59 => MlogRecordType::MlogInitFilePage2,
655 61 => MlogRecordType::MlogIndexLoad,
656 62 => MlogRecordType::MlogTableDynamicMeta,
657 63 => MlogRecordType::MlogPageCreateSdi,
658 64 => MlogRecordType::MlogCompPageCreateSdi,
659 65 => MlogRecordType::MlogFileExtend,
660 66 => MlogRecordType::MlogTest,
661 67 => MlogRecordType::MlogRecInsert,
662 68 => MlogRecordType::MlogRecClustDeleteMark,
663 69 => MlogRecordType::MlogRecDelete,
664 70 => MlogRecordType::MlogRecUpdateInPlace,
665 71 => MlogRecordType::MlogListEndCopyCreated,
666 72 => MlogRecordType::MlogPageReorganize,
667 73 => MlogRecordType::MlogZipPageReorganize,
668 74 => MlogRecordType::MlogZipPageCompressNoData,
669 75 => MlogRecordType::MlogListEndDelete,
670 76 => MlogRecordType::MlogListStartDelete,
671 v => MlogRecordType::Unknown(v),
672 }
673 }
674
675 pub fn name(&self) -> &str {
677 match self {
678 MlogRecordType::Mlog1Byte => "MLOG_1BYTE",
679 MlogRecordType::Mlog2Bytes => "MLOG_2BYTES",
680 MlogRecordType::Mlog4Bytes => "MLOG_4BYTES",
681 MlogRecordType::Mlog8Bytes => "MLOG_8BYTES",
682 MlogRecordType::MlogRecInsert8027 => "MLOG_REC_INSERT_8027",
683 MlogRecordType::MlogRecClustDeleteMark8027 => "MLOG_REC_CLUST_DELETE_MARK_8027",
684 MlogRecordType::MlogRecSecDeleteMark => "MLOG_REC_SEC_DELETE_MARK",
685 MlogRecordType::MlogRecUpdateInPlace8027 => "MLOG_REC_UPDATE_IN_PLACE_8027",
686 MlogRecordType::MlogRecDelete8027 => "MLOG_REC_DELETE_8027",
687 MlogRecordType::MlogListEndDelete8027 => "MLOG_LIST_END_DELETE_8027",
688 MlogRecordType::MlogListStartDelete8027 => "MLOG_LIST_START_DELETE_8027",
689 MlogRecordType::MlogListEndCopyCreated8027 => "MLOG_LIST_END_COPY_CREATED_8027",
690 MlogRecordType::MlogPageReorganize8027 => "MLOG_PAGE_REORGANIZE_8027",
691 MlogRecordType::MlogPageCreate => "MLOG_PAGE_CREATE",
692 MlogRecordType::MlogUndoInsert => "MLOG_UNDO_INSERT",
693 MlogRecordType::MlogUndoEraseEnd => "MLOG_UNDO_ERASE_END",
694 MlogRecordType::MlogUndoInit => "MLOG_UNDO_INIT",
695 MlogRecordType::MlogUndoHdrReuse => "MLOG_UNDO_HDR_REUSE",
696 MlogRecordType::MlogUndoHdrCreate => "MLOG_UNDO_HDR_CREATE",
697 MlogRecordType::MlogRecMinMark => "MLOG_REC_MIN_MARK",
698 MlogRecordType::MlogIbufBitmapInit => "MLOG_IBUF_BITMAP_INIT",
699 MlogRecordType::MlogLsn => "MLOG_LSN",
700 MlogRecordType::MlogInitFilePage => "MLOG_INIT_FILE_PAGE",
701 MlogRecordType::MlogWriteString => "MLOG_WRITE_STRING",
702 MlogRecordType::MlogMultiRecEnd => "MLOG_MULTI_REC_END",
703 MlogRecordType::MlogDummyRecord => "MLOG_DUMMY_RECORD",
704 MlogRecordType::MlogFileCreate => "MLOG_FILE_CREATE",
705 MlogRecordType::MlogFileRename => "MLOG_FILE_RENAME",
706 MlogRecordType::MlogFileDelete => "MLOG_FILE_DELETE",
707 MlogRecordType::MlogCompRecMinMark => "MLOG_COMP_REC_MIN_MARK",
708 MlogRecordType::MlogCompPageCreate => "MLOG_COMP_PAGE_CREATE",
709 MlogRecordType::MlogCompRecInsert8027 => "MLOG_COMP_REC_INSERT_8027",
710 MlogRecordType::MlogCompRecClustDeleteMark8027 => {
711 "MLOG_COMP_REC_CLUST_DELETE_MARK_8027"
712 }
713 MlogRecordType::MlogCompRecSecDeleteMark => "MLOG_COMP_REC_SEC_DELETE_MARK",
714 MlogRecordType::MlogCompRecUpdateInPlace8027 => "MLOG_COMP_REC_UPDATE_IN_PLACE_8027",
715 MlogRecordType::MlogCompRecDelete8027 => "MLOG_COMP_REC_DELETE_8027",
716 MlogRecordType::MlogCompListEndDelete8027 => "MLOG_COMP_LIST_END_DELETE_8027",
717 MlogRecordType::MlogCompListStartDelete8027 => "MLOG_COMP_LIST_START_DELETE_8027",
718 MlogRecordType::MlogCompListEndCopyCreated8027 => {
719 "MLOG_COMP_LIST_END_COPY_CREATED_8027"
720 }
721 MlogRecordType::MlogCompPageReorganize8027 => "MLOG_COMP_PAGE_REORGANIZE_8027",
722 MlogRecordType::MlogZipWriteNodePtr => "MLOG_ZIP_WRITE_NODE_PTR",
723 MlogRecordType::MlogZipWriteBlobPtr => "MLOG_ZIP_WRITE_BLOB_PTR",
724 MlogRecordType::MlogZipWriteHeader => "MLOG_ZIP_WRITE_HEADER",
725 MlogRecordType::MlogZipPageCompress => "MLOG_ZIP_PAGE_COMPRESS",
726 MlogRecordType::MlogZipPageCompressNoData8027 => "MLOG_ZIP_PAGE_COMPRESS_NO_DATA_8027",
727 MlogRecordType::MlogZipPageReorganize8027 => "MLOG_ZIP_PAGE_REORGANIZE_8027",
728 MlogRecordType::MlogPageCreateRTree => "MLOG_PAGE_CREATE_RTREE",
729 MlogRecordType::MlogCompPageCreateRTree => "MLOG_COMP_PAGE_CREATE_RTREE",
730 MlogRecordType::MlogInitFilePage2 => "MLOG_INIT_FILE_PAGE2",
731 MlogRecordType::MlogIndexLoad => "MLOG_INDEX_LOAD",
732 MlogRecordType::MlogTableDynamicMeta => "MLOG_TABLE_DYNAMIC_META",
733 MlogRecordType::MlogPageCreateSdi => "MLOG_PAGE_CREATE_SDI",
734 MlogRecordType::MlogCompPageCreateSdi => "MLOG_COMP_PAGE_CREATE_SDI",
735 MlogRecordType::MlogFileExtend => "MLOG_FILE_EXTEND",
736 MlogRecordType::MlogTest => "MLOG_TEST",
737 MlogRecordType::MlogRecInsert => "MLOG_REC_INSERT",
738 MlogRecordType::MlogRecClustDeleteMark => "MLOG_REC_CLUST_DELETE_MARK",
739 MlogRecordType::MlogRecDelete => "MLOG_REC_DELETE",
740 MlogRecordType::MlogRecUpdateInPlace => "MLOG_REC_UPDATE_IN_PLACE",
741 MlogRecordType::MlogListEndCopyCreated => "MLOG_LIST_END_COPY_CREATED",
742 MlogRecordType::MlogPageReorganize => "MLOG_PAGE_REORGANIZE",
743 MlogRecordType::MlogZipPageReorganize => "MLOG_ZIP_PAGE_REORGANIZE",
744 MlogRecordType::MlogZipPageCompressNoData => "MLOG_ZIP_PAGE_COMPRESS_NO_DATA",
745 MlogRecordType::MlogListEndDelete => "MLOG_LIST_END_DELETE",
746 MlogRecordType::MlogListStartDelete => "MLOG_LIST_START_DELETE",
747 MlogRecordType::Unknown(_) => "UNKNOWN",
748 }
749 }
750}
751
752impl std::fmt::Display for MlogRecordType {
753 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
754 match self {
755 MlogRecordType::Unknown(v) => write!(f, "UNKNOWN({})", v),
756 _ => write!(f, "{}", self.name()),
757 }
758 }
759}
760
761pub struct LogFile {
763 reader: Box<dyn ReadSeek>,
764 file_size: u64,
765}
766
767impl LogFile {
768 #[cfg(not(target_arch = "wasm32"))]
770 pub fn open(path: &str) -> Result<Self, IdbError> {
771 let file = std::fs::File::open(path)
772 .map_err(|e| IdbError::Io(format!("Cannot open {}: {}", path, e)))?;
773 let file_size = file
774 .metadata()
775 .map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", path, e)))?
776 .len();
777
778 Self::init(Box::new(file), file_size)
779 }
780
781 pub fn from_bytes(data: Vec<u8>) -> Result<Self, IdbError> {
798 let file_size = data.len() as u64;
799 Self::init(Box::new(Cursor::new(data)), file_size)
800 }
801
802 fn init(reader: Box<dyn ReadSeek>, file_size: u64) -> Result<Self, IdbError> {
803 if file_size < (LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE) as u64 {
804 return Err(IdbError::Parse(format!(
805 "File is too small for a redo log ({} bytes, minimum {})",
806 file_size,
807 LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE
808 )));
809 }
810
811 Ok(LogFile { reader, file_size })
812 }
813
814 pub fn block_count(&self) -> u64 {
816 self.file_size / LOG_BLOCK_SIZE as u64
817 }
818
819 pub fn data_block_count(&self) -> u64 {
821 self.block_count().saturating_sub(LOG_FILE_HDR_BLOCKS)
822 }
823
824 pub fn read_block(&mut self, block_no: u64) -> Result<Vec<u8>, IdbError> {
826 let offset = block_no * LOG_BLOCK_SIZE as u64;
827 if offset + LOG_BLOCK_SIZE as u64 > self.file_size {
828 return Err(IdbError::Io(format!(
829 "Block {} is beyond end of file (offset {}, file size {})",
830 block_no, offset, self.file_size
831 )));
832 }
833
834 self.reader
835 .seek(SeekFrom::Start(offset))
836 .map_err(|e| IdbError::Io(format!("Seek error: {}", e)))?;
837
838 let mut buf = vec![0u8; LOG_BLOCK_SIZE];
839 self.reader
840 .read_exact(&mut buf)
841 .map_err(|e| IdbError::Io(format!("Read error at block {}: {}", block_no, e)))?;
842
843 Ok(buf)
844 }
845
846 pub fn read_header(&mut self) -> Result<LogFileHeader, IdbError> {
848 let block = self.read_block(0)?;
849 LogFileHeader::parse(&block)
850 .ok_or_else(|| IdbError::Parse("Failed to parse log file header (block 0)".to_string()))
851 }
852
853 pub fn read_checkpoint(&mut self, slot: u8) -> Result<LogCheckpoint, IdbError> {
855 let block_no = match slot {
856 0 => 1,
857 1 => 3,
858 _ => {
859 return Err(IdbError::Argument(format!(
860 "Invalid checkpoint slot {} (must be 0 or 1)",
861 slot
862 )))
863 }
864 };
865 let block = self.read_block(block_no)?;
866 LogCheckpoint::parse(&block).ok_or_else(|| {
867 IdbError::Parse(format!("Failed to parse checkpoint at block {}", block_no))
868 })
869 }
870
871 pub fn file_size(&self) -> u64 {
873 self.file_size
874 }
875}
876
877#[cfg(test)]
878mod tests {
879 use super::*;
880
881 fn make_block() -> Vec<u8> {
882 vec![0u8; LOG_BLOCK_SIZE]
883 }
884
885 #[test]
886 fn test_log_block_header_parse() {
887 let mut block = make_block();
888 BigEndian::write_u32(&mut block[0..], 42);
890 BigEndian::write_u16(&mut block[4..], 200);
892 BigEndian::write_u16(&mut block[6..], 50);
894 BigEndian::write_u32(&mut block[8..], 7);
896
897 let hdr = LogBlockHeader::parse(&block).unwrap();
898 assert_eq!(hdr.block_no, 42);
899 assert!(!hdr.flush_flag);
900 assert_eq!(hdr.data_len, 200);
901 assert_eq!(hdr.first_rec_group, 50);
902 assert_eq!(hdr.epoch_no, 7);
903 assert_eq!(hdr.checkpoint_no(), 7);
904 assert!(hdr.has_data());
905 }
906
907 #[test]
908 fn test_log_block_flush_bit() {
909 let mut block = make_block();
910 BigEndian::write_u32(&mut block[0..], 0x80000064);
912 BigEndian::write_u16(&mut block[4..], 14); let hdr = LogBlockHeader::parse(&block).unwrap();
915 assert!(hdr.flush_flag);
916 assert_eq!(hdr.block_no, 100);
917 assert!(!hdr.has_data());
918 }
919
920 #[test]
921 fn test_log_block_header_empty() {
922 let block = make_block();
923 let hdr = LogBlockHeader::parse(&block).unwrap();
924 assert_eq!(hdr.block_no, 0);
925 assert!(!hdr.flush_flag);
926 assert_eq!(hdr.data_len, 0);
927 assert_eq!(hdr.first_rec_group, 0);
928 assert_eq!(hdr.epoch_no, 0);
929 assert!(!hdr.has_data());
930 }
931
932 #[test]
933 fn test_log_block_header_too_small() {
934 let block = vec![0u8; 10]; assert!(LogBlockHeader::parse(&block).is_none());
936 }
937
938 #[test]
939 fn test_log_block_trailer_parse() {
940 let mut block = make_block();
941 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xCAFEBABE);
942
943 let trailer = LogBlockTrailer::parse(&block).unwrap();
944 assert_eq!(trailer.checksum, 0xCAFEBABE);
945 }
946
947 #[test]
948 fn test_log_file_header_format_v6() {
949 let mut block = make_block();
950 BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 6);
951 BigEndian::write_u32(&mut block[LOG_HEADER_LOG_UUID..], 0xABCD1234);
952 BigEndian::write_u64(&mut block[LOG_HEADER_START_LSN..], 0x00000000001A2B3C);
953 let creator = b"MySQL 9.0.1";
954 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
955 .copy_from_slice(creator);
956
957 let hdr = LogFileHeader::parse(&block).unwrap();
958 assert_eq!(hdr.format_version, 6);
959 assert_eq!(hdr.group_id(), 6);
960 assert_eq!(hdr.start_lsn, 0x1A2B3C);
961 assert_eq!(hdr.log_uuid, 0xABCD1234);
962 assert_eq!(hdr.created_by, "MySQL 9.0.1");
963 }
964
965 #[test]
966 fn test_log_file_header_pre_8030() {
967 let mut block = make_block();
968 BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 1);
970 BigEndian::write_u64(&mut block[4..], 0x00000000001A2B3C); let creator = b"MySQL 5.7.44";
972 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
973 .copy_from_slice(creator);
974
975 let hdr = LogFileHeader::parse(&block).unwrap();
976 assert_eq!(hdr.format_version, 1);
977 assert_eq!(hdr.start_lsn, 0x1A2B3C);
978 assert_eq!(hdr.log_uuid, 0); assert_eq!(hdr.created_by, "MySQL 5.7.44");
980 }
981
982 #[test]
983 fn test_log_file_header_format_v5() {
984 let mut block = make_block();
985 BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 5);
987 BigEndian::write_u64(&mut block[4..], 0x00000000DEADBEEF);
988 let creator = b"MySQL 8.0.28";
989 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
990 .copy_from_slice(creator);
991
992 let hdr = LogFileHeader::parse(&block).unwrap();
993 assert_eq!(hdr.format_version, 5);
994 assert_eq!(hdr.start_lsn, 0xDEADBEEF);
995 assert_eq!(hdr.log_uuid, 0);
996 assert_eq!(hdr.created_by, "MySQL 8.0.28");
997 }
998
999 #[test]
1000 fn test_log_file_header_empty_created_by() {
1001 let block = make_block();
1002 let hdr = LogFileHeader::parse(&block).unwrap();
1003 assert_eq!(hdr.created_by, "");
1004 }
1005
1006 #[test]
1007 fn test_log_checkpoint_parse() {
1008 let mut block = make_block();
1009 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_NO..], 99);
1010 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 0x00000000DEADBEEF);
1011 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_OFFSET..], 2048);
1012 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_BUF_SIZE..], 65536);
1013 BigEndian::write_u64(
1014 &mut block[LOG_CHECKPOINT_ARCHIVED_LSN..],
1015 0x00000000CAFEBABE,
1016 );
1017
1018 let cp = LogCheckpoint::parse(&block).unwrap();
1019 assert_eq!(cp.number, 99);
1020 assert_eq!(cp.lsn, 0xDEADBEEF);
1021 assert_eq!(cp.offset, 2048);
1022 assert_eq!(cp.buf_size, 65536);
1023 assert_eq!(cp.archived_lsn, 0xCAFEBABE);
1024 }
1025
1026 #[test]
1027 fn test_log_checkpoint_format_v6() {
1028 let mut block = make_block();
1030 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 32193931);
1031
1032 let cp = LogCheckpoint::parse(&block).unwrap();
1033 assert_eq!(cp.lsn, 32193931);
1034 assert_eq!(cp.number, 0);
1035 assert_eq!(cp.offset, 0);
1036 assert_eq!(cp.buf_size, 0);
1037 assert_eq!(cp.archived_lsn, 0);
1038 }
1039
1040 #[test]
1041 fn test_mlog_record_type_basic_writes() {
1042 assert_eq!(MlogRecordType::from_u8(1), MlogRecordType::Mlog1Byte);
1043 assert_eq!(MlogRecordType::from_u8(2), MlogRecordType::Mlog2Bytes);
1044 assert_eq!(MlogRecordType::from_u8(4), MlogRecordType::Mlog4Bytes);
1045 assert_eq!(MlogRecordType::from_u8(8), MlogRecordType::Mlog8Bytes);
1046 }
1047
1048 #[test]
1049 fn test_mlog_record_type_pre_8028() {
1050 assert_eq!(
1052 MlogRecordType::from_u8(9),
1053 MlogRecordType::MlogRecInsert8027
1054 );
1055 assert_eq!(
1056 MlogRecordType::from_u8(10),
1057 MlogRecordType::MlogRecClustDeleteMark8027
1058 );
1059 assert_eq!(
1060 MlogRecordType::from_u8(18),
1061 MlogRecordType::MlogPageReorganize8027
1062 );
1063 assert_eq!(
1064 MlogRecordType::from_u8(38),
1065 MlogRecordType::MlogCompRecInsert8027
1066 );
1067 assert_eq!(
1068 MlogRecordType::from_u8(52),
1069 MlogRecordType::MlogZipPageCompressNoData8027
1070 );
1071 assert_eq!(
1072 MlogRecordType::from_u8(53),
1073 MlogRecordType::MlogZipPageReorganize8027
1074 );
1075 }
1076
1077 #[test]
1078 fn test_mlog_record_type_corrected_mappings() {
1079 assert_eq!(
1081 MlogRecordType::from_u8(25),
1082 MlogRecordType::MlogUndoHdrCreate
1083 );
1084 assert_eq!(MlogRecordType::from_u8(26), MlogRecordType::MlogRecMinMark);
1085 assert_eq!(
1086 MlogRecordType::from_u8(27),
1087 MlogRecordType::MlogIbufBitmapInit
1088 );
1089 assert_eq!(MlogRecordType::from_u8(28), MlogRecordType::MlogLsn);
1090 assert_eq!(
1091 MlogRecordType::from_u8(29),
1092 MlogRecordType::MlogInitFilePage
1093 );
1094 assert_eq!(MlogRecordType::from_u8(30), MlogRecordType::MlogWriteString);
1095 assert_eq!(MlogRecordType::from_u8(31), MlogRecordType::MlogMultiRecEnd);
1096 assert_eq!(MlogRecordType::from_u8(32), MlogRecordType::MlogDummyRecord);
1097 assert_eq!(MlogRecordType::from_u8(33), MlogRecordType::MlogFileCreate);
1098 assert_eq!(MlogRecordType::from_u8(34), MlogRecordType::MlogFileRename);
1099 assert_eq!(MlogRecordType::from_u8(35), MlogRecordType::MlogFileDelete);
1100 assert_eq!(
1101 MlogRecordType::from_u8(36),
1102 MlogRecordType::MlogCompRecMinMark
1103 );
1104 assert_eq!(
1105 MlogRecordType::from_u8(37),
1106 MlogRecordType::MlogCompPageCreate
1107 );
1108 }
1109
1110 #[test]
1111 fn test_mlog_record_type_post_8028() {
1112 assert_eq!(MlogRecordType::from_u8(67), MlogRecordType::MlogRecInsert);
1114 assert_eq!(
1115 MlogRecordType::from_u8(68),
1116 MlogRecordType::MlogRecClustDeleteMark
1117 );
1118 assert_eq!(MlogRecordType::from_u8(69), MlogRecordType::MlogRecDelete);
1119 assert_eq!(
1120 MlogRecordType::from_u8(70),
1121 MlogRecordType::MlogRecUpdateInPlace
1122 );
1123 assert_eq!(
1124 MlogRecordType::from_u8(71),
1125 MlogRecordType::MlogListEndCopyCreated
1126 );
1127 assert_eq!(
1128 MlogRecordType::from_u8(72),
1129 MlogRecordType::MlogPageReorganize
1130 );
1131 assert_eq!(
1132 MlogRecordType::from_u8(73),
1133 MlogRecordType::MlogZipPageReorganize
1134 );
1135 assert_eq!(
1136 MlogRecordType::from_u8(74),
1137 MlogRecordType::MlogZipPageCompressNoData
1138 );
1139 assert_eq!(
1140 MlogRecordType::from_u8(75),
1141 MlogRecordType::MlogListEndDelete
1142 );
1143 assert_eq!(
1144 MlogRecordType::from_u8(76),
1145 MlogRecordType::MlogListStartDelete
1146 );
1147 }
1148
1149 #[test]
1150 fn test_mlog_record_type_extended() {
1151 assert_eq!(
1153 MlogRecordType::from_u8(57),
1154 MlogRecordType::MlogPageCreateRTree
1155 );
1156 assert_eq!(
1157 MlogRecordType::from_u8(59),
1158 MlogRecordType::MlogInitFilePage2
1159 );
1160 assert_eq!(MlogRecordType::from_u8(61), MlogRecordType::MlogIndexLoad);
1161 assert_eq!(
1162 MlogRecordType::from_u8(62),
1163 MlogRecordType::MlogTableDynamicMeta
1164 );
1165 assert_eq!(
1166 MlogRecordType::from_u8(63),
1167 MlogRecordType::MlogPageCreateSdi
1168 );
1169 assert_eq!(
1170 MlogRecordType::from_u8(64),
1171 MlogRecordType::MlogCompPageCreateSdi
1172 );
1173 assert_eq!(MlogRecordType::from_u8(65), MlogRecordType::MlogFileExtend);
1174 assert_eq!(MlogRecordType::from_u8(66), MlogRecordType::MlogTest);
1175 }
1176
1177 #[test]
1178 fn test_mlog_record_type_unknown() {
1179 assert_eq!(MlogRecordType::from_u8(0), MlogRecordType::Unknown(0));
1180 assert_eq!(MlogRecordType::from_u8(255), MlogRecordType::Unknown(255));
1181 assert_eq!(MlogRecordType::from_u8(100), MlogRecordType::Unknown(100));
1182 assert_eq!(MlogRecordType::from_u8(3), MlogRecordType::Unknown(3));
1184 assert_eq!(MlogRecordType::from_u8(12), MlogRecordType::Unknown(12));
1185 assert_eq!(MlogRecordType::from_u8(23), MlogRecordType::Unknown(23));
1186 assert_eq!(MlogRecordType::from_u8(47), MlogRecordType::Unknown(47));
1187 assert_eq!(MlogRecordType::from_u8(60), MlogRecordType::Unknown(60));
1188 assert_eq!(MlogRecordType::from_u8(77), MlogRecordType::Unknown(77));
1189 }
1190
1191 #[test]
1192 fn test_mlog_record_type_name() {
1193 assert_eq!(MlogRecordType::Mlog1Byte.name(), "MLOG_1BYTE");
1194 assert_eq!(MlogRecordType::MlogRecInsert.name(), "MLOG_REC_INSERT");
1195 assert_eq!(
1196 MlogRecordType::MlogRecInsert8027.name(),
1197 "MLOG_REC_INSERT_8027"
1198 );
1199 assert_eq!(MlogRecordType::MlogFileExtend.name(), "MLOG_FILE_EXTEND");
1200 assert_eq!(MlogRecordType::MlogLsn.name(), "MLOG_LSN");
1201 assert_eq!(MlogRecordType::Unknown(99).name(), "UNKNOWN");
1202 }
1203
1204 #[test]
1205 fn test_mlog_record_type_display() {
1206 assert_eq!(format!("{}", MlogRecordType::Mlog1Byte), "MLOG_1BYTE");
1207 assert_eq!(format!("{}", MlogRecordType::Unknown(99)), "UNKNOWN(99)");
1208 assert_eq!(
1209 format!("{}", MlogRecordType::MlogListEndDelete),
1210 "MLOG_LIST_END_DELETE"
1211 );
1212 }
1213
1214 #[test]
1215 fn test_log_block_checksum_validation() {
1216 let mut block = make_block();
1217 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]);
1225 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], crc);
1226
1227 assert!(validate_log_block_checksum(&block));
1228 }
1229
1230 #[test]
1231 fn test_log_from_bytes_empty() {
1232 let result = LogFile::from_bytes(vec![]);
1233 match result {
1234 Err(e) => assert!(
1235 e.to_string().contains("too small"),
1236 "Expected 'too small' in: {e}"
1237 ),
1238 Ok(_) => panic!("Expected error for empty input"),
1239 }
1240 }
1241
1242 #[test]
1243 fn test_log_from_bytes_too_small() {
1244 let result = LogFile::from_bytes(vec![0u8; 100]);
1245 match result {
1246 Err(e) => assert!(
1247 e.to_string().contains("too small"),
1248 "Expected 'too small' in: {e}"
1249 ),
1250 Ok(_) => panic!("Expected error for 100-byte input"),
1251 }
1252 }
1253
1254 #[test]
1255 fn test_log_block_checksum_invalid() {
1256 let mut block = make_block();
1257 BigEndian::write_u32(&mut block[0..], 5);
1258 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xDEADDEAD);
1260
1261 assert!(!validate_log_block_checksum(&block));
1262 }
1263}