Skip to main content

idb/cli/
sdi.rs

1use std::io::Write;
2
3use crate::cli::wprintln;
4use crate::innodb::sdi;
5use crate::innodb::tablespace::Tablespace;
6use crate::IdbError;
7
8/// Options for the `inno sdi` subcommand.
9pub struct SdiOptions {
10    /// Path to the InnoDB tablespace file (.ibd).
11    pub file: String,
12    /// Pretty-print the extracted JSON metadata.
13    pub pretty: bool,
14    /// Override the auto-detected page size.
15    pub page_size: Option<u32>,
16}
17
18/// Extract SDI metadata from a MySQL 8.0+ tablespace.
19///
20/// SDI (Serialized Dictionary Information) is MySQL 8.0's mechanism for
21/// embedding the data dictionary (table definitions, column metadata, index
22/// descriptions) directly inside each `.ibd` file, replacing the `.frm` files
23/// used in MySQL 5.x. SDI data is stored in pages of type 17853
24/// (`FIL_PAGE_SDI`).
25///
26/// This command first scans the tablespace for SDI pages by checking page
27/// types, then uses [`sdi::extract_sdi_from_pages`]
28/// to reassemble records that may span multiple pages via the next-page chain.
29/// Each record's zlib-compressed payload is decompressed into a JSON string
30/// containing the full table/column/index definition.
31///
32/// With `--pretty`, the JSON is re-parsed and re-serialized with indentation
33/// for readability. If a tablespace has no SDI pages (e.g., pre-8.0 files),
34/// a message is printed indicating that SDI is unavailable.
35pub fn execute(opts: &SdiOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
36    let mut ts = match opts.page_size {
37        Some(ps) => Tablespace::open_with_page_size(&opts.file, ps)?,
38        None => Tablespace::open(&opts.file)?,
39    };
40
41    // Find SDI pages
42    let sdi_pages = sdi::find_sdi_pages(&mut ts)?;
43
44    if sdi_pages.is_empty() {
45        wprintln!(writer, "No SDI pages found in {}.", opts.file)?;
46        wprintln!(writer, "SDI is only available in MySQL 8.0+ tablespaces.")?;
47        return Ok(());
48    }
49
50    wprintln!(
51        writer,
52        "Found {} SDI page(s): {:?}",
53        sdi_pages.len(),
54        sdi_pages
55    )?;
56
57    // Use multi-page reassembly to extract records
58    let records = sdi::extract_sdi_from_pages(&mut ts, &sdi_pages)?;
59
60    if records.is_empty() {
61        wprintln!(
62            writer,
63            "No SDI records found (pages may be non-leaf or empty)."
64        )?;
65        return Ok(());
66    }
67
68    for rec in &records {
69        wprintln!(writer)?;
70        wprintln!(
71            writer,
72            "=== SDI Record: type={} ({}), id={}",
73            rec.sdi_type,
74            sdi::sdi_type_name(rec.sdi_type),
75            rec.sdi_id
76        )?;
77        wprintln!(
78            writer,
79            "Compressed: {} bytes, Uncompressed: {} bytes",
80            rec.compressed_len,
81            rec.uncompressed_len
82        )?;
83
84        if rec.data.is_empty() {
85            wprintln!(
86                writer,
87                "(Data could not be decompressed - may span multiple pages)"
88            )?;
89            continue;
90        }
91
92        if opts.pretty {
93            // Pretty-print JSON
94            match serde_json::from_str::<serde_json::Value>(&rec.data) {
95                Ok(json) => {
96                    wprintln!(
97                        writer,
98                        "{}",
99                        serde_json::to_string_pretty(&json).unwrap_or(rec.data.clone())
100                    )?;
101                }
102                Err(_) => {
103                    wprintln!(writer, "{}", rec.data)?;
104                }
105            }
106        } else {
107            wprintln!(writer, "{}", rec.data)?;
108        }
109    }
110
111    wprintln!(writer)?;
112    wprintln!(writer, "Total SDI records: {}", records.len())?;
113
114    Ok(())
115}