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    // MariaDB does not use SDI — return a clear error
42    if ts.vendor_info().vendor == crate::innodb::vendor::InnoDbVendor::MariaDB {
43        return Err(IdbError::Argument(
44            "SDI is not available for MariaDB tablespaces. MariaDB does not use \
45             Serialized Dictionary Information (SDI); table metadata is stored \
46             in the data dictionary (mysql.* tables) or .frm files."
47                .to_string(),
48        ));
49    }
50
51    // Find SDI pages
52    let sdi_pages = sdi::find_sdi_pages(&mut ts)?;
53
54    if sdi_pages.is_empty() {
55        wprintln!(writer, "No SDI pages found in {}.", opts.file)?;
56        wprintln!(writer, "SDI is only available in MySQL 8.0+ tablespaces.")?;
57        return Ok(());
58    }
59
60    wprintln!(
61        writer,
62        "Found {} SDI page(s): {:?}",
63        sdi_pages.len(),
64        sdi_pages
65    )?;
66
67    // Use multi-page reassembly to extract records
68    let records = sdi::extract_sdi_from_pages(&mut ts, &sdi_pages)?;
69
70    if records.is_empty() {
71        wprintln!(
72            writer,
73            "No SDI records found (pages may be non-leaf or empty)."
74        )?;
75        return Ok(());
76    }
77
78    for rec in &records {
79        wprintln!(writer)?;
80        wprintln!(
81            writer,
82            "=== SDI Record: type={} ({}), id={}",
83            rec.sdi_type,
84            sdi::sdi_type_name(rec.sdi_type),
85            rec.sdi_id
86        )?;
87        wprintln!(
88            writer,
89            "Compressed: {} bytes, Uncompressed: {} bytes",
90            rec.compressed_len,
91            rec.uncompressed_len
92        )?;
93
94        if rec.data.is_empty() {
95            wprintln!(
96                writer,
97                "(Data could not be decompressed - may span multiple pages)"
98            )?;
99            continue;
100        }
101
102        if opts.pretty {
103            // Pretty-print JSON
104            match serde_json::from_str::<serde_json::Value>(&rec.data) {
105                Ok(json) => {
106                    wprintln!(
107                        writer,
108                        "{}",
109                        serde_json::to_string_pretty(&json).unwrap_or(rec.data.clone())
110                    )?;
111                }
112                Err(_) => {
113                    wprintln!(writer, "{}", rec.data)?;
114                }
115            }
116        } else {
117            wprintln!(writer, "{}", rec.data)?;
118        }
119    }
120
121    wprintln!(writer)?;
122    wprintln!(writer, "Total SDI records: {}", records.len())?;
123
124    Ok(())
125}