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
761#[derive(Debug, Clone, Serialize)]
775pub struct MlogRecord {
776 pub block_offset: usize,
778 pub record_type: MlogRecordType,
780 pub single_rec: bool,
782 #[serde(skip_serializing_if = "Option::is_none")]
784 pub space_id: Option<u32>,
785 #[serde(skip_serializing_if = "Option::is_none")]
787 pub page_no: Option<u32>,
788}
789
790impl MlogRecordType {
791 fn is_non_page_type(&self) -> bool {
795 matches!(
796 self,
797 MlogRecordType::MlogMultiRecEnd
798 | MlogRecordType::MlogDummyRecord
799 | MlogRecordType::MlogFileCreate
800 | MlogRecordType::MlogFileRename
801 | MlogRecordType::MlogFileDelete
802 | MlogRecordType::MlogFileExtend
803 | MlogRecordType::MlogLsn
804 | MlogRecordType::MlogTest
805 | MlogRecordType::MlogTableDynamicMeta
806 )
807 }
808
809 fn is_known_type_code(code: u8) -> bool {
811 matches!(
812 code,
813 1..=2 | 4 | 8..=11 | 13..=22 | 24..=35 | 36..=46 | 48..=53 | 57..=59 | 61..=76
814 )
815 }
816}
817
818pub fn parse_mlog_records(block: &[u8], hdr: &LogBlockHeader) -> Vec<MlogRecord> {
830 let data_end = std::cmp::min(hdr.data_len as usize, block.len());
831 if data_end <= LOG_BLOCK_HDR_SIZE {
832 return Vec::new();
833 }
834
835 let mut records = Vec::new();
836 let mut pos = LOG_BLOCK_HDR_SIZE;
837
838 while pos < data_end {
839 let raw_type = block[pos];
840 if raw_type == 0 {
842 pos += 1;
843 continue;
844 }
845
846 let single_rec = (raw_type & 0x80) != 0;
847 let type_code = raw_type & 0x7F;
848
849 if !MlogRecordType::is_known_type_code(type_code) {
851 pos += 1;
852 continue;
853 }
854
855 let record_type = MlogRecordType::from_u8(type_code);
856 let block_offset = pos;
857 pos += 1; if record_type.is_non_page_type() {
860 records.push(MlogRecord {
861 block_offset,
862 record_type,
863 single_rec,
864 space_id: None,
865 page_no: None,
866 });
867 continue;
868 }
869
870 let (space_id, page_no) = if let Some((sid, sid_len)) =
872 crate::innodb::undo::read_compressed(block, pos)
873 {
874 let next = pos + sid_len;
875 if sid <= 0x00FF_FFFF {
876 if let Some((pno, pno_len)) = crate::innodb::undo::read_compressed(block, next) {
878 pos = next + pno_len;
879 (Some(sid as u32), Some(pno as u32))
880 } else {
881 pos = next;
882 (Some(sid as u32), None)
883 }
884 } else {
885 (None, None)
887 }
888 } else {
889 (None, None)
890 };
891
892 records.push(MlogRecord {
893 block_offset,
894 record_type,
895 single_rec,
896 space_id,
897 page_no,
898 });
899 }
900
901 records
902}
903
904pub fn compute_record_lsn(start_lsn: u64, block_idx: u64, payload_offset: usize) -> u64 {
916 const PAYLOAD_PER_BLOCK: u64 =
917 (LOG_BLOCK_SIZE - LOG_BLOCK_HDR_SIZE - LOG_BLOCK_TRL_SIZE) as u64;
918 let offset_in_payload = payload_offset.saturating_sub(LOG_BLOCK_HDR_SIZE) as u64;
919 start_lsn + block_idx * PAYLOAD_PER_BLOCK + offset_in_payload
920}
921
922pub struct LogFile {
924 reader: Box<dyn ReadSeek>,
925 file_size: u64,
926}
927
928impl LogFile {
929 #[cfg(not(target_arch = "wasm32"))]
931 pub fn open(path: &str) -> Result<Self, IdbError> {
932 let file = std::fs::File::open(path)
933 .map_err(|e| IdbError::Io(format!("Cannot open {}: {}", path, e)))?;
934 let file_size = file
935 .metadata()
936 .map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", path, e)))?
937 .len();
938
939 Self::init(Box::new(file), file_size)
940 }
941
942 pub fn from_bytes(data: Vec<u8>) -> Result<Self, IdbError> {
959 let file_size = data.len() as u64;
960 Self::init(Box::new(Cursor::new(data)), file_size)
961 }
962
963 fn init(reader: Box<dyn ReadSeek>, file_size: u64) -> Result<Self, IdbError> {
964 if file_size < (LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE) as u64 {
965 return Err(IdbError::Parse(format!(
966 "File is too small for a redo log ({} bytes, minimum {})",
967 file_size,
968 LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE
969 )));
970 }
971
972 Ok(LogFile { reader, file_size })
973 }
974
975 pub fn block_count(&self) -> u64 {
977 self.file_size / LOG_BLOCK_SIZE as u64
978 }
979
980 pub fn data_block_count(&self) -> u64 {
982 self.block_count().saturating_sub(LOG_FILE_HDR_BLOCKS)
983 }
984
985 pub fn read_block(&mut self, block_no: u64) -> Result<Vec<u8>, IdbError> {
987 let offset = block_no * LOG_BLOCK_SIZE as u64;
988 if offset + LOG_BLOCK_SIZE as u64 > self.file_size {
989 return Err(IdbError::Io(format!(
990 "Block {} is beyond end of file (offset {}, file size {})",
991 block_no, offset, self.file_size
992 )));
993 }
994
995 self.reader
996 .seek(SeekFrom::Start(offset))
997 .map_err(|e| IdbError::Io(format!("Seek error: {}", e)))?;
998
999 let mut buf = vec![0u8; LOG_BLOCK_SIZE];
1000 self.reader
1001 .read_exact(&mut buf)
1002 .map_err(|e| IdbError::Io(format!("Read error at block {}: {}", block_no, e)))?;
1003
1004 Ok(buf)
1005 }
1006
1007 pub fn read_header(&mut self) -> Result<LogFileHeader, IdbError> {
1009 let block = self.read_block(0)?;
1010 LogFileHeader::parse(&block)
1011 .ok_or_else(|| IdbError::Parse("Failed to parse log file header (block 0)".to_string()))
1012 }
1013
1014 pub fn read_checkpoint(&mut self, slot: u8) -> Result<LogCheckpoint, IdbError> {
1016 let block_no = match slot {
1017 0 => 1,
1018 1 => 3,
1019 _ => {
1020 return Err(IdbError::Argument(format!(
1021 "Invalid checkpoint slot {} (must be 0 or 1)",
1022 slot
1023 )))
1024 }
1025 };
1026 let block = self.read_block(block_no)?;
1027 LogCheckpoint::parse(&block).ok_or_else(|| {
1028 IdbError::Parse(format!("Failed to parse checkpoint at block {}", block_no))
1029 })
1030 }
1031
1032 pub fn file_size(&self) -> u64 {
1034 self.file_size
1035 }
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040 use super::*;
1041
1042 fn make_block() -> Vec<u8> {
1043 vec![0u8; LOG_BLOCK_SIZE]
1044 }
1045
1046 #[test]
1047 fn test_log_block_header_parse() {
1048 let mut block = make_block();
1049 BigEndian::write_u32(&mut block[0..], 42);
1051 BigEndian::write_u16(&mut block[4..], 200);
1053 BigEndian::write_u16(&mut block[6..], 50);
1055 BigEndian::write_u32(&mut block[8..], 7);
1057
1058 let hdr = LogBlockHeader::parse(&block).unwrap();
1059 assert_eq!(hdr.block_no, 42);
1060 assert!(!hdr.flush_flag);
1061 assert_eq!(hdr.data_len, 200);
1062 assert_eq!(hdr.first_rec_group, 50);
1063 assert_eq!(hdr.epoch_no, 7);
1064 assert_eq!(hdr.checkpoint_no(), 7);
1065 assert!(hdr.has_data());
1066 }
1067
1068 #[test]
1069 fn test_log_block_flush_bit() {
1070 let mut block = make_block();
1071 BigEndian::write_u32(&mut block[0..], 0x80000064);
1073 BigEndian::write_u16(&mut block[4..], 14); let hdr = LogBlockHeader::parse(&block).unwrap();
1076 assert!(hdr.flush_flag);
1077 assert_eq!(hdr.block_no, 100);
1078 assert!(!hdr.has_data());
1079 }
1080
1081 #[test]
1082 fn test_log_block_header_empty() {
1083 let block = make_block();
1084 let hdr = LogBlockHeader::parse(&block).unwrap();
1085 assert_eq!(hdr.block_no, 0);
1086 assert!(!hdr.flush_flag);
1087 assert_eq!(hdr.data_len, 0);
1088 assert_eq!(hdr.first_rec_group, 0);
1089 assert_eq!(hdr.epoch_no, 0);
1090 assert!(!hdr.has_data());
1091 }
1092
1093 #[test]
1094 fn test_log_block_header_too_small() {
1095 let block = vec![0u8; 10]; assert!(LogBlockHeader::parse(&block).is_none());
1097 }
1098
1099 #[test]
1100 fn test_log_block_trailer_parse() {
1101 let mut block = make_block();
1102 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xCAFEBABE);
1103
1104 let trailer = LogBlockTrailer::parse(&block).unwrap();
1105 assert_eq!(trailer.checksum, 0xCAFEBABE);
1106 }
1107
1108 #[test]
1109 fn test_log_file_header_format_v6() {
1110 let mut block = make_block();
1111 BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 6);
1112 BigEndian::write_u32(&mut block[LOG_HEADER_LOG_UUID..], 0xABCD1234);
1113 BigEndian::write_u64(&mut block[LOG_HEADER_START_LSN..], 0x00000000001A2B3C);
1114 let creator = b"MySQL 9.0.1";
1115 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
1116 .copy_from_slice(creator);
1117
1118 let hdr = LogFileHeader::parse(&block).unwrap();
1119 assert_eq!(hdr.format_version, 6);
1120 assert_eq!(hdr.group_id(), 6);
1121 assert_eq!(hdr.start_lsn, 0x1A2B3C);
1122 assert_eq!(hdr.log_uuid, 0xABCD1234);
1123 assert_eq!(hdr.created_by, "MySQL 9.0.1");
1124 }
1125
1126 #[test]
1127 fn test_log_file_header_pre_8030() {
1128 let mut block = make_block();
1129 BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 1);
1131 BigEndian::write_u64(&mut block[4..], 0x00000000001A2B3C); let creator = b"MySQL 5.7.44";
1133 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
1134 .copy_from_slice(creator);
1135
1136 let hdr = LogFileHeader::parse(&block).unwrap();
1137 assert_eq!(hdr.format_version, 1);
1138 assert_eq!(hdr.start_lsn, 0x1A2B3C);
1139 assert_eq!(hdr.log_uuid, 0); assert_eq!(hdr.created_by, "MySQL 5.7.44");
1141 }
1142
1143 #[test]
1144 fn test_log_file_header_format_v5() {
1145 let mut block = make_block();
1146 BigEndian::write_u32(&mut block[LOG_HEADER_FORMAT..], 5);
1148 BigEndian::write_u64(&mut block[4..], 0x00000000DEADBEEF);
1149 let creator = b"MySQL 8.0.28";
1150 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
1151 .copy_from_slice(creator);
1152
1153 let hdr = LogFileHeader::parse(&block).unwrap();
1154 assert_eq!(hdr.format_version, 5);
1155 assert_eq!(hdr.start_lsn, 0xDEADBEEF);
1156 assert_eq!(hdr.log_uuid, 0);
1157 assert_eq!(hdr.created_by, "MySQL 8.0.28");
1158 }
1159
1160 #[test]
1161 fn test_log_file_header_empty_created_by() {
1162 let block = make_block();
1163 let hdr = LogFileHeader::parse(&block).unwrap();
1164 assert_eq!(hdr.created_by, "");
1165 }
1166
1167 #[test]
1168 fn test_log_checkpoint_parse() {
1169 let mut block = make_block();
1170 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_NO..], 99);
1171 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 0x00000000DEADBEEF);
1172 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_OFFSET..], 2048);
1173 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_BUF_SIZE..], 65536);
1174 BigEndian::write_u64(
1175 &mut block[LOG_CHECKPOINT_ARCHIVED_LSN..],
1176 0x00000000CAFEBABE,
1177 );
1178
1179 let cp = LogCheckpoint::parse(&block).unwrap();
1180 assert_eq!(cp.number, 99);
1181 assert_eq!(cp.lsn, 0xDEADBEEF);
1182 assert_eq!(cp.offset, 2048);
1183 assert_eq!(cp.buf_size, 65536);
1184 assert_eq!(cp.archived_lsn, 0xCAFEBABE);
1185 }
1186
1187 #[test]
1188 fn test_log_checkpoint_format_v6() {
1189 let mut block = make_block();
1191 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 32193931);
1192
1193 let cp = LogCheckpoint::parse(&block).unwrap();
1194 assert_eq!(cp.lsn, 32193931);
1195 assert_eq!(cp.number, 0);
1196 assert_eq!(cp.offset, 0);
1197 assert_eq!(cp.buf_size, 0);
1198 assert_eq!(cp.archived_lsn, 0);
1199 }
1200
1201 #[test]
1202 fn test_mlog_record_type_basic_writes() {
1203 assert_eq!(MlogRecordType::from_u8(1), MlogRecordType::Mlog1Byte);
1204 assert_eq!(MlogRecordType::from_u8(2), MlogRecordType::Mlog2Bytes);
1205 assert_eq!(MlogRecordType::from_u8(4), MlogRecordType::Mlog4Bytes);
1206 assert_eq!(MlogRecordType::from_u8(8), MlogRecordType::Mlog8Bytes);
1207 }
1208
1209 #[test]
1210 fn test_mlog_record_type_pre_8028() {
1211 assert_eq!(
1213 MlogRecordType::from_u8(9),
1214 MlogRecordType::MlogRecInsert8027
1215 );
1216 assert_eq!(
1217 MlogRecordType::from_u8(10),
1218 MlogRecordType::MlogRecClustDeleteMark8027
1219 );
1220 assert_eq!(
1221 MlogRecordType::from_u8(18),
1222 MlogRecordType::MlogPageReorganize8027
1223 );
1224 assert_eq!(
1225 MlogRecordType::from_u8(38),
1226 MlogRecordType::MlogCompRecInsert8027
1227 );
1228 assert_eq!(
1229 MlogRecordType::from_u8(52),
1230 MlogRecordType::MlogZipPageCompressNoData8027
1231 );
1232 assert_eq!(
1233 MlogRecordType::from_u8(53),
1234 MlogRecordType::MlogZipPageReorganize8027
1235 );
1236 }
1237
1238 #[test]
1239 fn test_mlog_record_type_corrected_mappings() {
1240 assert_eq!(
1242 MlogRecordType::from_u8(25),
1243 MlogRecordType::MlogUndoHdrCreate
1244 );
1245 assert_eq!(MlogRecordType::from_u8(26), MlogRecordType::MlogRecMinMark);
1246 assert_eq!(
1247 MlogRecordType::from_u8(27),
1248 MlogRecordType::MlogIbufBitmapInit
1249 );
1250 assert_eq!(MlogRecordType::from_u8(28), MlogRecordType::MlogLsn);
1251 assert_eq!(
1252 MlogRecordType::from_u8(29),
1253 MlogRecordType::MlogInitFilePage
1254 );
1255 assert_eq!(MlogRecordType::from_u8(30), MlogRecordType::MlogWriteString);
1256 assert_eq!(MlogRecordType::from_u8(31), MlogRecordType::MlogMultiRecEnd);
1257 assert_eq!(MlogRecordType::from_u8(32), MlogRecordType::MlogDummyRecord);
1258 assert_eq!(MlogRecordType::from_u8(33), MlogRecordType::MlogFileCreate);
1259 assert_eq!(MlogRecordType::from_u8(34), MlogRecordType::MlogFileRename);
1260 assert_eq!(MlogRecordType::from_u8(35), MlogRecordType::MlogFileDelete);
1261 assert_eq!(
1262 MlogRecordType::from_u8(36),
1263 MlogRecordType::MlogCompRecMinMark
1264 );
1265 assert_eq!(
1266 MlogRecordType::from_u8(37),
1267 MlogRecordType::MlogCompPageCreate
1268 );
1269 }
1270
1271 #[test]
1272 fn test_mlog_record_type_post_8028() {
1273 assert_eq!(MlogRecordType::from_u8(67), MlogRecordType::MlogRecInsert);
1275 assert_eq!(
1276 MlogRecordType::from_u8(68),
1277 MlogRecordType::MlogRecClustDeleteMark
1278 );
1279 assert_eq!(MlogRecordType::from_u8(69), MlogRecordType::MlogRecDelete);
1280 assert_eq!(
1281 MlogRecordType::from_u8(70),
1282 MlogRecordType::MlogRecUpdateInPlace
1283 );
1284 assert_eq!(
1285 MlogRecordType::from_u8(71),
1286 MlogRecordType::MlogListEndCopyCreated
1287 );
1288 assert_eq!(
1289 MlogRecordType::from_u8(72),
1290 MlogRecordType::MlogPageReorganize
1291 );
1292 assert_eq!(
1293 MlogRecordType::from_u8(73),
1294 MlogRecordType::MlogZipPageReorganize
1295 );
1296 assert_eq!(
1297 MlogRecordType::from_u8(74),
1298 MlogRecordType::MlogZipPageCompressNoData
1299 );
1300 assert_eq!(
1301 MlogRecordType::from_u8(75),
1302 MlogRecordType::MlogListEndDelete
1303 );
1304 assert_eq!(
1305 MlogRecordType::from_u8(76),
1306 MlogRecordType::MlogListStartDelete
1307 );
1308 }
1309
1310 #[test]
1311 fn test_mlog_record_type_extended() {
1312 assert_eq!(
1314 MlogRecordType::from_u8(57),
1315 MlogRecordType::MlogPageCreateRTree
1316 );
1317 assert_eq!(
1318 MlogRecordType::from_u8(59),
1319 MlogRecordType::MlogInitFilePage2
1320 );
1321 assert_eq!(MlogRecordType::from_u8(61), MlogRecordType::MlogIndexLoad);
1322 assert_eq!(
1323 MlogRecordType::from_u8(62),
1324 MlogRecordType::MlogTableDynamicMeta
1325 );
1326 assert_eq!(
1327 MlogRecordType::from_u8(63),
1328 MlogRecordType::MlogPageCreateSdi
1329 );
1330 assert_eq!(
1331 MlogRecordType::from_u8(64),
1332 MlogRecordType::MlogCompPageCreateSdi
1333 );
1334 assert_eq!(MlogRecordType::from_u8(65), MlogRecordType::MlogFileExtend);
1335 assert_eq!(MlogRecordType::from_u8(66), MlogRecordType::MlogTest);
1336 }
1337
1338 #[test]
1339 fn test_mlog_record_type_unknown() {
1340 assert_eq!(MlogRecordType::from_u8(0), MlogRecordType::Unknown(0));
1341 assert_eq!(MlogRecordType::from_u8(255), MlogRecordType::Unknown(255));
1342 assert_eq!(MlogRecordType::from_u8(100), MlogRecordType::Unknown(100));
1343 assert_eq!(MlogRecordType::from_u8(3), MlogRecordType::Unknown(3));
1345 assert_eq!(MlogRecordType::from_u8(12), MlogRecordType::Unknown(12));
1346 assert_eq!(MlogRecordType::from_u8(23), MlogRecordType::Unknown(23));
1347 assert_eq!(MlogRecordType::from_u8(47), MlogRecordType::Unknown(47));
1348 assert_eq!(MlogRecordType::from_u8(60), MlogRecordType::Unknown(60));
1349 assert_eq!(MlogRecordType::from_u8(77), MlogRecordType::Unknown(77));
1350 }
1351
1352 #[test]
1353 fn test_mlog_record_type_name() {
1354 assert_eq!(MlogRecordType::Mlog1Byte.name(), "MLOG_1BYTE");
1355 assert_eq!(MlogRecordType::MlogRecInsert.name(), "MLOG_REC_INSERT");
1356 assert_eq!(
1357 MlogRecordType::MlogRecInsert8027.name(),
1358 "MLOG_REC_INSERT_8027"
1359 );
1360 assert_eq!(MlogRecordType::MlogFileExtend.name(), "MLOG_FILE_EXTEND");
1361 assert_eq!(MlogRecordType::MlogLsn.name(), "MLOG_LSN");
1362 assert_eq!(MlogRecordType::Unknown(99).name(), "UNKNOWN");
1363 }
1364
1365 #[test]
1366 fn test_mlog_record_type_display() {
1367 assert_eq!(format!("{}", MlogRecordType::Mlog1Byte), "MLOG_1BYTE");
1368 assert_eq!(format!("{}", MlogRecordType::Unknown(99)), "UNKNOWN(99)");
1369 assert_eq!(
1370 format!("{}", MlogRecordType::MlogListEndDelete),
1371 "MLOG_LIST_END_DELETE"
1372 );
1373 }
1374
1375 #[test]
1376 fn test_log_block_checksum_validation() {
1377 let mut block = make_block();
1378 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]);
1386 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], crc);
1387
1388 assert!(validate_log_block_checksum(&block));
1389 }
1390
1391 #[test]
1392 fn test_log_from_bytes_empty() {
1393 let result = LogFile::from_bytes(vec![]);
1394 match result {
1395 Err(e) => assert!(
1396 e.to_string().contains("too small"),
1397 "Expected 'too small' in: {e}"
1398 ),
1399 Ok(_) => panic!("Expected error for empty input"),
1400 }
1401 }
1402
1403 #[test]
1404 fn test_log_from_bytes_too_small() {
1405 let result = LogFile::from_bytes(vec![0u8; 100]);
1406 match result {
1407 Err(e) => assert!(
1408 e.to_string().contains("too small"),
1409 "Expected 'too small' in: {e}"
1410 ),
1411 Ok(_) => panic!("Expected error for 100-byte input"),
1412 }
1413 }
1414
1415 #[test]
1416 fn test_log_block_checksum_invalid() {
1417 let mut block = make_block();
1418 BigEndian::write_u32(&mut block[0..], 5);
1419 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xDEADDEAD);
1421
1422 assert!(!validate_log_block_checksum(&block));
1423 }
1424}