use byteorder::{BigEndian, ByteOrder};
use serde::Serialize;
use crate::innodb::constants::{FIL_NULL, FIL_PAGE_DATA};
const LOB_HDR_PART_LEN: usize = 0; const LOB_HDR_NEXT_PAGE_NO: usize = 4; const LOB_HDR_SIZE: usize = 8;
const LOB_FIRST_VERSION: usize = 0; const LOB_FIRST_FLAGS: usize = 1; const LOB_FIRST_DATA_LEN: usize = 2; const LOB_FIRST_TRX_ID: usize = 6; const LOB_FIRST_HDR_SIZE: usize = 12;
const LOB_INDEX_ENTRY_SIZE: usize = 60;
const LOB_DATA_VERSION: usize = 0; const LOB_DATA_DATA_LEN: usize = 1; const LOB_DATA_TRX_ID: usize = 5; const LOB_DATA_HDR_SIZE: usize = 11;
const ZLOB_FIRST_VERSION: usize = 0; const ZLOB_FIRST_FLAGS: usize = 1; const ZLOB_FIRST_DATA_LEN: usize = 2; const ZLOB_FIRST_TRX_ID: usize = 6; const ZLOB_FIRST_HDR_SIZE: usize = 12;
const ZLOB_DATA_VERSION: usize = 0; const ZLOB_DATA_DATA_LEN: usize = 1; const ZLOB_DATA_TRX_ID: usize = 5; const ZLOB_DATA_HDR_SIZE: usize = 11;
#[derive(Debug, Clone, Serialize)]
pub struct BlobPageHeader {
pub part_len: u32,
pub next_page_no: u32,
}
impl BlobPageHeader {
pub fn parse(page_data: &[u8]) -> Option<Self> {
let base = FIL_PAGE_DATA;
if page_data.len() < base + LOB_HDR_SIZE {
return None;
}
let d = &page_data[base..];
Some(BlobPageHeader {
part_len: BigEndian::read_u32(&d[LOB_HDR_PART_LEN..]),
next_page_no: BigEndian::read_u32(&d[LOB_HDR_NEXT_PAGE_NO..]),
})
}
pub fn has_next(&self) -> bool {
self.next_page_no != FIL_NULL && self.next_page_no != 0
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LobFirstPageHeader {
pub version: u8,
pub flags: u8,
pub data_len: u32,
pub trx_id: u64,
}
impl LobFirstPageHeader {
pub fn parse(page_data: &[u8]) -> Option<Self> {
let base = FIL_PAGE_DATA;
if page_data.len() < base + LOB_FIRST_HDR_SIZE {
return None;
}
let d = &page_data[base..];
let trx_id = if d.len() >= LOB_FIRST_TRX_ID + 6 {
let mut buf = [0u8; 8];
buf[2..8].copy_from_slice(&d[LOB_FIRST_TRX_ID..LOB_FIRST_TRX_ID + 6]);
BigEndian::read_u64(&buf)
} else {
0
};
Some(LobFirstPageHeader {
version: d[LOB_FIRST_VERSION],
flags: d[LOB_FIRST_FLAGS],
data_len: BigEndian::read_u32(&d[LOB_FIRST_DATA_LEN..]),
trx_id,
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LobIndexEntry {
pub prev_page_no: u32,
pub prev_offset: u16,
pub next_page_no: u32,
pub next_offset: u16,
pub versions: u8,
pub trx_id: u64,
pub trx_undo_no: u32,
pub page_no: u32,
pub data_len: u32,
pub lob_version: u32,
}
impl LobIndexEntry {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < LOB_INDEX_ENTRY_SIZE {
return None;
}
let mut trx_buf = [0u8; 8];
trx_buf[2..8].copy_from_slice(&data[14..20]);
Some(LobIndexEntry {
prev_page_no: BigEndian::read_u32(&data[0..]),
prev_offset: BigEndian::read_u16(&data[4..]),
next_page_no: BigEndian::read_u32(&data[6..]),
next_offset: BigEndian::read_u16(&data[10..]),
versions: data[12],
trx_id: BigEndian::read_u64(&trx_buf),
trx_undo_no: BigEndian::read_u32(&data[20..]),
page_no: BigEndian::read_u32(&data[24..]),
data_len: BigEndian::read_u32(&data[28..]),
lob_version: BigEndian::read_u32(&data[32..]),
})
}
pub fn parse_all(page_data: &[u8], start_offset: usize) -> Vec<Self> {
let mut entries = Vec::new();
let mut offset = start_offset;
while offset + LOB_INDEX_ENTRY_SIZE <= page_data.len() {
if let Some(entry) = LobIndexEntry::parse(&page_data[offset..]) {
if entry.page_no != FIL_NULL {
entries.push(entry);
}
offset += LOB_INDEX_ENTRY_SIZE;
} else {
break;
}
}
entries
}
}
#[derive(Debug, Clone, Serialize)]
pub struct LobDataPageHeader {
pub version: u8,
pub data_len: u32,
pub trx_id: u64,
}
impl LobDataPageHeader {
pub fn parse(page_data: &[u8]) -> Option<Self> {
let base = FIL_PAGE_DATA;
if page_data.len() < base + LOB_DATA_HDR_SIZE {
return None;
}
let d = &page_data[base..];
let mut trx_buf = [0u8; 8];
trx_buf[2..8].copy_from_slice(&d[LOB_DATA_TRX_ID..LOB_DATA_TRX_ID + 6]);
Some(LobDataPageHeader {
version: d[LOB_DATA_VERSION],
data_len: BigEndian::read_u32(&d[LOB_DATA_DATA_LEN..]),
trx_id: BigEndian::read_u64(&trx_buf),
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ZlobFirstPageHeader {
pub version: u8,
pub flags: u8,
pub data_len: u32,
pub trx_id: u64,
}
impl ZlobFirstPageHeader {
pub fn parse(page_data: &[u8]) -> Option<Self> {
let base = FIL_PAGE_DATA;
if page_data.len() < base + ZLOB_FIRST_HDR_SIZE {
return None;
}
let d = &page_data[base..];
let mut trx_buf = [0u8; 8];
trx_buf[2..8].copy_from_slice(&d[ZLOB_FIRST_TRX_ID..ZLOB_FIRST_TRX_ID + 6]);
Some(ZlobFirstPageHeader {
version: d[ZLOB_FIRST_VERSION],
flags: d[ZLOB_FIRST_FLAGS],
data_len: BigEndian::read_u32(&d[ZLOB_FIRST_DATA_LEN..]),
trx_id: BigEndian::read_u64(&trx_buf),
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ZlobDataPageHeader {
pub version: u8,
pub data_len: u32,
pub trx_id: u64,
}
impl ZlobDataPageHeader {
pub fn parse(page_data: &[u8]) -> Option<Self> {
let base = FIL_PAGE_DATA;
if page_data.len() < base + ZLOB_DATA_HDR_SIZE {
return None;
}
let d = &page_data[base..];
let mut trx_buf = [0u8; 8];
trx_buf[2..8].copy_from_slice(&d[ZLOB_DATA_TRX_ID..ZLOB_DATA_TRX_ID + 6]);
Some(ZlobDataPageHeader {
version: d[ZLOB_DATA_VERSION],
data_len: BigEndian::read_u32(&d[ZLOB_DATA_DATA_LEN..]),
trx_id: BigEndian::read_u64(&trx_buf),
})
}
}
pub fn walk_blob_chain(
ts: &mut crate::innodb::tablespace::Tablespace,
start_page: u64,
max_pages: usize,
) -> Result<Vec<(u64, u32)>, crate::IdbError> {
let mut chain = Vec::new();
let mut current = start_page;
for _ in 0..max_pages {
if current == FIL_NULL as u64 || current == 0 {
break;
}
let page_data = ts.read_page(current)?;
let hdr = match BlobPageHeader::parse(&page_data) {
Some(h) => h,
None => break,
};
chain.push((current, hdr.part_len));
if !hdr.has_next() {
break;
}
current = hdr.next_page_no as u64;
}
Ok(chain)
}
use crate::innodb::page::FilHeader;
use crate::innodb::page_types::PageType;
#[derive(Debug, Clone, Serialize)]
pub struct LobChainPage {
pub page_no: u64,
pub page_type: String,
pub data_len: u32,
}
#[derive(Debug, Clone, Serialize)]
pub struct LobChainInfo {
pub first_page: u64,
pub total_data_len: u64,
pub page_count: usize,
pub chain_type: String,
pub pages: Vec<LobChainPage>,
}
pub fn walk_lob_chain(
ts: &mut crate::innodb::tablespace::Tablespace,
start_page: u64,
max_pages: usize,
) -> Result<Option<LobChainInfo>, crate::IdbError> {
let page_data = ts.read_page(start_page)?;
let fil_hdr = match FilHeader::parse(&page_data) {
Some(h) => h,
None => return Ok(None),
};
match fil_hdr.page_type {
PageType::Blob | PageType::ZBlob | PageType::ZBlob2 => {
walk_old_blob_chain(ts, start_page, max_pages)
}
PageType::LobFirst => walk_new_lob_chain(ts, start_page, &page_data, max_pages),
PageType::ZlobFirst => walk_zlob_chain(ts, start_page, &page_data, max_pages),
_ => Ok(None),
}
}
fn walk_old_blob_chain(
ts: &mut crate::innodb::tablespace::Tablespace,
start_page: u64,
max_pages: usize,
) -> Result<Option<LobChainInfo>, crate::IdbError> {
let chain = walk_blob_chain(ts, start_page, max_pages)?;
let pages: Vec<LobChainPage> = chain
.iter()
.map(|(page_no, part_len)| LobChainPage {
page_no: *page_no,
page_type: "BLOB".to_string(),
data_len: *part_len,
})
.collect();
let total_data_len: u64 = pages.iter().map(|p| p.data_len as u64).sum();
Ok(Some(LobChainInfo {
first_page: start_page,
total_data_len,
page_count: pages.len(),
chain_type: "old_blob".to_string(),
pages,
}))
}
fn walk_new_lob_chain(
ts: &mut crate::innodb::tablespace::Tablespace,
start_page: u64,
first_page_data: &[u8],
max_pages: usize,
) -> Result<Option<LobChainInfo>, crate::IdbError> {
let lob_hdr = match LobFirstPageHeader::parse(first_page_data) {
Some(h) => h,
None => return Ok(None),
};
let mut pages = vec![LobChainPage {
page_no: start_page,
page_type: "LOB_FIRST".to_string(),
data_len: lob_hdr.data_len,
}];
let index_start = FIL_PAGE_DATA + LOB_FIRST_HDR_SIZE;
let entries = LobIndexEntry::parse_all(first_page_data, index_start);
for entry in entries.iter().take(max_pages.saturating_sub(1)) {
let data_page = match ts.read_page(entry.page_no as u64) {
Ok(d) => d,
Err(_) => continue,
};
let page_type_name = match FilHeader::parse(&data_page).map(|h| h.page_type) {
Some(PageType::LobData) => "LOB_DATA",
Some(PageType::LobIndex) => "LOB_INDEX",
_ => "LOB_PAGE",
};
pages.push(LobChainPage {
page_no: entry.page_no as u64,
page_type: page_type_name.to_string(),
data_len: entry.data_len,
});
}
let total_data_len: u64 = pages.iter().map(|p| p.data_len as u64).sum();
Ok(Some(LobChainInfo {
first_page: start_page,
total_data_len,
page_count: pages.len(),
chain_type: "lob".to_string(),
pages,
}))
}
fn walk_zlob_chain(
ts: &mut crate::innodb::tablespace::Tablespace,
start_page: u64,
first_page_data: &[u8],
max_pages: usize,
) -> Result<Option<LobChainInfo>, crate::IdbError> {
let zlob_hdr = match ZlobFirstPageHeader::parse(first_page_data) {
Some(h) => h,
None => return Ok(None),
};
let mut pages = vec![LobChainPage {
page_no: start_page,
page_type: "ZLOB_FIRST".to_string(),
data_len: zlob_hdr.data_len,
}];
let index_start = FIL_PAGE_DATA + ZLOB_FIRST_HDR_SIZE;
let entries = LobIndexEntry::parse_all(first_page_data, index_start);
for entry in entries.iter().take(max_pages.saturating_sub(1)) {
let data_page = match ts.read_page(entry.page_no as u64) {
Ok(d) => d,
Err(_) => continue,
};
let page_type_name = match FilHeader::parse(&data_page).map(|h| h.page_type) {
Some(PageType::ZlobData) => "ZLOB_DATA",
Some(PageType::ZlobFrag) => "ZLOB_FRAG",
Some(PageType::ZlobFragEntry) => "ZLOB_FRAG_ENTRY",
_ => "ZLOB_PAGE",
};
pages.push(LobChainPage {
page_no: entry.page_no as u64,
page_type: page_type_name.to_string(),
data_len: entry.data_len,
});
}
let total_data_len: u64 = pages.iter().map(|p| p.data_len as u64).sum();
Ok(Some(LobChainInfo {
first_page: start_page,
total_data_len,
page_count: pages.len(),
chain_type: "zlob".to_string(),
pages,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blob_page_header_parse() {
let mut page = vec![0u8; 256];
let base = FIL_PAGE_DATA;
BigEndian::write_u32(&mut page[base + LOB_HDR_PART_LEN..], 8000);
BigEndian::write_u32(&mut page[base + LOB_HDR_NEXT_PAGE_NO..], 42);
let hdr = BlobPageHeader::parse(&page).unwrap();
assert_eq!(hdr.part_len, 8000);
assert_eq!(hdr.next_page_no, 42);
assert!(hdr.has_next());
}
#[test]
fn test_blob_page_header_no_next() {
let mut page = vec![0u8; 256];
let base = FIL_PAGE_DATA;
BigEndian::write_u32(&mut page[base + LOB_HDR_PART_LEN..], 5000);
BigEndian::write_u32(&mut page[base + LOB_HDR_NEXT_PAGE_NO..], FIL_NULL);
let hdr = BlobPageHeader::parse(&page).unwrap();
assert_eq!(hdr.part_len, 5000);
assert!(!hdr.has_next());
}
#[test]
fn test_lob_first_page_header_parse() {
let mut page = vec![0u8; 256];
let base = FIL_PAGE_DATA;
page[base + LOB_FIRST_VERSION] = 1;
page[base + LOB_FIRST_FLAGS] = 0;
BigEndian::write_u32(&mut page[base + LOB_FIRST_DATA_LEN..], 100_000);
let trx_bytes = 12345u64.to_be_bytes();
page[base + LOB_FIRST_TRX_ID..base + LOB_FIRST_TRX_ID + 6]
.copy_from_slice(&trx_bytes[2..8]);
let hdr = LobFirstPageHeader::parse(&page).unwrap();
assert_eq!(hdr.version, 1);
assert_eq!(hdr.flags, 0);
assert_eq!(hdr.data_len, 100_000);
assert_eq!(hdr.trx_id, 12345);
}
#[test]
fn test_lob_index_entry_parse() {
let mut entry = vec![0u8; 60];
BigEndian::write_u32(&mut entry[0..], 10);
BigEndian::write_u16(&mut entry[4..], 100);
BigEndian::write_u32(&mut entry[6..], 20);
BigEndian::write_u16(&mut entry[10..], 200);
entry[12] = 2;
let trx_bytes = 5000u64.to_be_bytes();
entry[14..20].copy_from_slice(&trx_bytes[2..8]);
BigEndian::write_u32(&mut entry[20..], 42);
BigEndian::write_u32(&mut entry[24..], 7);
BigEndian::write_u32(&mut entry[28..], 16000);
BigEndian::write_u32(&mut entry[32..], 1);
let parsed = LobIndexEntry::parse(&entry).unwrap();
assert_eq!(parsed.prev_page_no, 10);
assert_eq!(parsed.prev_offset, 100);
assert_eq!(parsed.next_page_no, 20);
assert_eq!(parsed.next_offset, 200);
assert_eq!(parsed.versions, 2);
assert_eq!(parsed.trx_id, 5000);
assert_eq!(parsed.trx_undo_no, 42);
assert_eq!(parsed.page_no, 7);
assert_eq!(parsed.data_len, 16000);
assert_eq!(parsed.lob_version, 1);
}
#[test]
fn test_lob_index_entry_too_short() {
let entry = vec![0u8; 30]; assert!(LobIndexEntry::parse(&entry).is_none());
}
#[test]
fn test_lob_index_entry_parse_all() {
let mut data = vec![0u8; 60 * 3];
BigEndian::write_u32(&mut data[24..], 5); BigEndian::write_u32(&mut data[28..], 8000); BigEndian::write_u32(&mut data[60 + 24..], 6); BigEndian::write_u32(&mut data[60 + 28..], 4000);
BigEndian::write_u32(&mut data[120 + 24..], FIL_NULL);
let entries = LobIndexEntry::parse_all(&data, 0);
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].page_no, 5);
assert_eq!(entries[1].page_no, 6);
}
#[test]
fn test_lob_data_page_header_parse() {
let mut page = vec![0u8; 60];
let base = FIL_PAGE_DATA;
page[base] = 1; BigEndian::write_u32(&mut page[base + 1..], 8000);
let trx_bytes = 777u64.to_be_bytes();
page[base + 5..base + 11].copy_from_slice(&trx_bytes[2..8]);
let hdr = LobDataPageHeader::parse(&page).unwrap();
assert_eq!(hdr.version, 1);
assert_eq!(hdr.data_len, 8000);
assert_eq!(hdr.trx_id, 777);
}
#[test]
fn test_zlob_first_page_header_parse() {
let mut page = vec![0u8; 60];
let base = FIL_PAGE_DATA;
page[base] = 1;
page[base + 1] = 0;
BigEndian::write_u32(&mut page[base + 2..], 500_000);
let trx_bytes = 1234u64.to_be_bytes();
page[base + 6..base + 12].copy_from_slice(&trx_bytes[2..8]);
let hdr = ZlobFirstPageHeader::parse(&page).unwrap();
assert_eq!(hdr.version, 1);
assert_eq!(hdr.flags, 0);
assert_eq!(hdr.data_len, 500_000);
assert_eq!(hdr.trx_id, 1234);
}
#[test]
fn test_zlob_data_page_header_parse() {
let mut page = vec![0u8; 60];
let base = FIL_PAGE_DATA;
page[base] = 1;
BigEndian::write_u32(&mut page[base + 1..], 4096);
let trx_bytes = 999u64.to_be_bytes();
page[base + 5..base + 11].copy_from_slice(&trx_bytes[2..8]);
let hdr = ZlobDataPageHeader::parse(&page).unwrap();
assert_eq!(hdr.version, 1);
assert_eq!(hdr.data_len, 4096);
assert_eq!(hdr.trx_id, 999);
}
#[test]
fn test_lob_data_page_header_too_short() {
let page = vec![0u8; 40]; assert!(LobDataPageHeader::parse(&page).is_none());
}
}