Skip to main content

idb/innodb/
compression.rs

1use flate2::read::ZlibDecoder;
2use std::io::Read;
3
4/// Compression algorithm detected or used for a page.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum CompressionAlgorithm {
7    None,
8    Zlib,
9    Lz4,
10}
11
12/// Detect the compression algorithm from FSP space flags.
13///
14/// Compression type is stored in bits 11-12 of FSP flags (MySQL 8.0+).
15/// - 0 = none
16/// - 1 = zlib
17/// - 2 = lz4
18pub fn detect_compression(fsp_flags: u32) -> CompressionAlgorithm {
19    let comp_bits = (fsp_flags >> 11) & 0x03;
20    match comp_bits {
21        1 => CompressionAlgorithm::Zlib,
22        2 => CompressionAlgorithm::Lz4,
23        _ => CompressionAlgorithm::None,
24    }
25}
26
27/// Decompress zlib-compressed page data.
28///
29/// Returns the decompressed data, or None if decompression fails.
30pub fn decompress_zlib(compressed: &[u8]) -> Option<Vec<u8>> {
31    let mut decoder = ZlibDecoder::new(compressed);
32    let mut decompressed = Vec::new();
33    decoder.read_to_end(&mut decompressed).ok()?;
34    Some(decompressed)
35}
36
37/// Decompress LZ4-compressed page data.
38///
39/// `uncompressed_len` is the expected output size (typically the page size).
40/// Returns the decompressed data, or None if decompression fails.
41pub fn decompress_lz4(compressed: &[u8], uncompressed_len: usize) -> Option<Vec<u8>> {
42    lz4_flex::decompress(compressed, uncompressed_len).ok()
43}
44
45/// Check if a page appears to be a hole-punched page.
46///
47/// Hole-punched pages have their data zeroed out after the compressed content.
48/// The FIL header is preserved, and the actual data is followed by trailing zeros.
49pub fn is_hole_punched(page_data: &[u8], page_size: u32) -> bool {
50    if page_data.len() < page_size as usize {
51        return false;
52    }
53
54    // A hole-punched page has trailing zeros. Check the last quarter of the page.
55    let check_start = (page_size as usize * 3) / 4;
56    page_data[check_start..page_size as usize]
57        .iter()
58        .all(|&b| b == 0)
59}
60
61impl std::fmt::Display for CompressionAlgorithm {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            CompressionAlgorithm::None => write!(f, "None"),
65            CompressionAlgorithm::Zlib => write!(f, "Zlib"),
66            CompressionAlgorithm::Lz4 => write!(f, "LZ4"),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_detect_compression() {
77        assert_eq!(detect_compression(0), CompressionAlgorithm::None);
78        assert_eq!(detect_compression(1 << 11), CompressionAlgorithm::Zlib);
79        assert_eq!(detect_compression(2 << 11), CompressionAlgorithm::Lz4);
80        assert_eq!(detect_compression(3 << 11), CompressionAlgorithm::None);
81        // Other bits set shouldn't affect compression detection
82        assert_eq!(detect_compression(0xFF | (1 << 11)), CompressionAlgorithm::Zlib);
83    }
84
85    #[test]
86    fn test_decompress_zlib() {
87        use flate2::write::ZlibEncoder;
88        use flate2::Compression;
89        use std::io::Write;
90
91        let original = b"Hello, InnoDB compression test data!";
92        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
93        encoder.write_all(original).unwrap();
94        let compressed = encoder.finish().unwrap();
95
96        let result = decompress_zlib(&compressed).unwrap();
97        assert_eq!(result, original);
98    }
99
100    #[test]
101    fn test_decompress_lz4() {
102        let original = b"Hello, LZ4 compression test data for InnoDB!";
103        let compressed = lz4_flex::compress_prepend_size(original);
104        // lz4_flex::compress_prepend_size adds 4-byte length prefix,
105        // but decompress expects just the compressed data with known length
106        let result = lz4_flex::decompress(&compressed[4..], original.len());
107        assert!(result.is_ok());
108        assert_eq!(result.unwrap(), original);
109    }
110
111    #[test]
112    fn test_is_hole_punched() {
113        let page_size = 16384u32;
114        let mut page = vec![0u8; page_size as usize];
115        // All zeros = hole punched
116        assert!(is_hole_punched(&page, page_size));
117
118        // Some data in the first part, zeros in the last quarter
119        page[0] = 0xFF;
120        page[100] = 0xAB;
121        assert!(is_hole_punched(&page, page_size));
122
123        // Non-zero byte in the last quarter = not hole punched
124        page[page_size as usize - 10] = 0x01;
125        assert!(!is_hole_punched(&page, page_size));
126    }
127}