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!(writer, "Found {} SDI page(s): {:?}", sdi_pages.len(), sdi_pages)?;
51
52    // Use multi-page reassembly to extract records
53    let records = sdi::extract_sdi_from_pages(&mut ts, &sdi_pages)?;
54
55    if records.is_empty() {
56        wprintln!(writer, "No SDI records found (pages may be non-leaf or empty).")?;
57        return Ok(());
58    }
59
60    for rec in &records {
61        wprintln!(writer)?;
62        wprintln!(
63            writer,
64            "=== SDI Record: type={} ({}), id={}",
65            rec.sdi_type,
66            sdi::sdi_type_name(rec.sdi_type),
67            rec.sdi_id
68        )?;
69        wprintln!(
70            writer,
71            "Compressed: {} bytes, Uncompressed: {} bytes",
72            rec.compressed_len, rec.uncompressed_len
73        )?;
74
75        if rec.data.is_empty() {
76            wprintln!(writer, "(Data could not be decompressed - may span multiple pages)")?;
77            continue;
78        }
79
80        if opts.pretty {
81            // Pretty-print JSON
82            match serde_json::from_str::<serde_json::Value>(&rec.data) {
83                Ok(json) => {
84                    wprintln!(
85                        writer,
86                        "{}",
87                        serde_json::to_string_pretty(&json).unwrap_or(rec.data.clone())
88                    )?;
89                }
90                Err(_) => {
91                    wprintln!(writer, "{}", rec.data)?;
92                }
93            }
94        } else {
95            wprintln!(writer, "{}", rec.data)?;
96        }
97    }
98
99    wprintln!(writer)?;
100    wprintln!(writer, "Total SDI records: {}", records.len())?;
101
102    Ok(())
103}