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