use byteorder::{BigEndian, ByteOrder};
use serde::Serialize;
use crate::innodb::index::IndexHeader;
const MBR_SIZE: usize = 32;
#[derive(Debug, Clone, Serialize)]
pub struct MinimumBoundingRectangle {
pub min_x: f64,
pub min_y: f64,
pub max_x: f64,
pub max_y: f64,
}
impl MinimumBoundingRectangle {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < MBR_SIZE {
return None;
}
Some(MinimumBoundingRectangle {
min_x: BigEndian::read_f64(&data[0..]),
min_y: BigEndian::read_f64(&data[8..]),
max_x: BigEndian::read_f64(&data[16..]),
max_y: BigEndian::read_f64(&data[24..]),
})
}
pub fn area(&self) -> f64 {
(self.max_x - self.min_x) * (self.max_y - self.min_y)
}
pub fn enclosing(mbrs: &[MinimumBoundingRectangle]) -> Option<MinimumBoundingRectangle> {
if mbrs.is_empty() {
return None;
}
let mut min_x = f64::MAX;
let mut min_y = f64::MAX;
let mut max_x = f64::MIN;
let mut max_y = f64::MIN;
for m in mbrs {
if m.min_x < min_x {
min_x = m.min_x;
}
if m.min_y < min_y {
min_y = m.min_y;
}
if m.max_x > max_x {
max_x = m.max_x;
}
if m.max_y > max_y {
max_y = m.max_y;
}
}
Some(MinimumBoundingRectangle {
min_x,
min_y,
max_x,
max_y,
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct RtreePageInfo {
pub level: u16,
pub record_count: u16,
pub mbrs: Vec<MinimumBoundingRectangle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enclosing_mbr: Option<MinimumBoundingRectangle>,
}
pub fn parse_rtree_page(page_data: &[u8]) -> Option<RtreePageInfo> {
let idx_hdr = IndexHeader::parse(page_data)?;
let record_area_start = 120;
let mut mbrs = Vec::new();
let n_recs = idx_hdr.n_recs as usize;
let mut offset = record_area_start;
let record_header_size = 5;
for _ in 0..n_recs {
let mbr_start = offset + record_header_size;
if mbr_start + MBR_SIZE > page_data.len() {
break;
}
if let Some(mbr) = MinimumBoundingRectangle::parse(&page_data[mbr_start..]) {
if mbr.min_x.is_finite()
&& mbr.min_y.is_finite()
&& mbr.max_x.is_finite()
&& mbr.max_y.is_finite()
{
mbrs.push(mbr);
}
}
offset += record_header_size + MBR_SIZE + 4;
}
let enclosing_mbr = MinimumBoundingRectangle::enclosing(&mbrs);
Some(RtreePageInfo {
level: idx_hdr.level,
record_count: idx_hdr.n_recs,
mbrs,
enclosing_mbr,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::innodb::constants::*;
#[test]
fn test_mbr_parse() {
let mut buf = vec![0u8; 32];
BigEndian::write_f64(&mut buf[0..], 1.0);
BigEndian::write_f64(&mut buf[8..], 2.0);
BigEndian::write_f64(&mut buf[16..], 3.0);
BigEndian::write_f64(&mut buf[24..], 4.0);
let mbr = MinimumBoundingRectangle::parse(&buf).unwrap();
assert_eq!(mbr.min_x, 1.0);
assert_eq!(mbr.min_y, 2.0);
assert_eq!(mbr.max_x, 3.0);
assert_eq!(mbr.max_y, 4.0);
}
#[test]
fn test_mbr_too_short() {
let buf = vec![0u8; 20];
assert!(MinimumBoundingRectangle::parse(&buf).is_none());
}
#[test]
fn test_mbr_area() {
let mbr = MinimumBoundingRectangle {
min_x: 0.0,
min_y: 0.0,
max_x: 10.0,
max_y: 5.0,
};
assert!((mbr.area() - 50.0).abs() < f64::EPSILON);
}
#[test]
fn test_mbr_enclosing() {
let mbrs = vec![
MinimumBoundingRectangle {
min_x: 1.0,
min_y: 2.0,
max_x: 5.0,
max_y: 6.0,
},
MinimumBoundingRectangle {
min_x: 3.0,
min_y: 1.0,
max_x: 10.0,
max_y: 8.0,
},
];
let enc = MinimumBoundingRectangle::enclosing(&mbrs).unwrap();
assert_eq!(enc.min_x, 1.0);
assert_eq!(enc.min_y, 1.0);
assert_eq!(enc.max_x, 10.0);
assert_eq!(enc.max_y, 8.0);
}
#[test]
fn test_mbr_enclosing_empty() {
assert!(MinimumBoundingRectangle::enclosing(&[]).is_none());
}
#[test]
fn test_parse_rtree_page_basic() {
let mut page = vec![0u8; 256];
let base = FIL_PAGE_DATA;
BigEndian::write_u16(&mut page[base + PAGE_LEVEL..], 0);
BigEndian::write_u16(&mut page[base + PAGE_N_RECS..], 1);
let mbr_offset = 125;
BigEndian::write_f64(&mut page[mbr_offset..], 10.0);
BigEndian::write_f64(&mut page[mbr_offset + 8..], 20.0);
BigEndian::write_f64(&mut page[mbr_offset + 16..], 30.0);
BigEndian::write_f64(&mut page[mbr_offset + 24..], 40.0);
let info = parse_rtree_page(&page).unwrap();
assert_eq!(info.level, 0);
assert_eq!(info.record_count, 1);
assert_eq!(info.mbrs.len(), 1);
assert_eq!(info.mbrs[0].min_x, 10.0);
assert_eq!(info.mbrs[0].max_y, 40.0);
assert!(info.enclosing_mbr.is_some());
}
#[test]
fn test_parse_rtree_page_too_short() {
let page = vec![0u8; 30];
assert!(parse_rtree_page(&page).is_none());
}
}