use std::io::Write;
use crate::cli::wprintln;
use crate::innodb::schema::{self, InferredSchema, TableSchema};
use crate::innodb::sdi;
use crate::IdbError;
pub struct SchemaOptions {
pub file: String,
pub verbose: bool,
pub json: bool,
pub page_size: Option<u32>,
pub keyring: Option<String>,
pub mmap: bool,
}
pub fn execute(opts: &SchemaOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
let mut ts = crate::cli::open_tablespace(&opts.file, opts.page_size, opts.mmap)?;
if let Some(ref keyring_path) = opts.keyring {
crate::cli::setup_decryption(&mut ts, keyring_path)?;
}
if ts.vendor_info().vendor == crate::innodb::vendor::InnoDbVendor::MariaDB {
let inferred = schema::infer_schema_from_pages(&mut ts)?;
if opts.json {
wprintln!(
writer,
"{}",
serde_json::to_string_pretty(&inferred)
.map_err(|e| IdbError::Parse(e.to_string()))?
)?;
} else {
print_inferred_text(writer, &inferred)?;
}
return Ok(());
}
let sdi_pages = sdi::find_sdi_pages(&mut ts)?;
if sdi_pages.is_empty() {
let inferred = schema::infer_schema_from_pages(&mut ts)?;
if opts.json {
wprintln!(
writer,
"{}",
serde_json::to_string_pretty(&inferred)
.map_err(|e| IdbError::Parse(e.to_string()))?
)?;
} else {
print_inferred_text(writer, &inferred)?;
}
return Ok(());
}
let records = sdi::extract_sdi_from_pages(&mut ts, &sdi_pages)?;
let table_records: Vec<_> = records.iter().filter(|r| r.sdi_type == 1).collect();
if table_records.is_empty() {
wprintln!(writer, "No table SDI records found in {}.", opts.file)?;
return Ok(());
}
for rec in &table_records {
let table_schema = schema::extract_schema_from_sdi(&rec.data)?;
if opts.json {
wprintln!(
writer,
"{}",
serde_json::to_string_pretty(&table_schema)
.map_err(|e| IdbError::Parse(e.to_string()))?
)?;
} else if opts.verbose {
print_verbose_text(writer, &table_schema)?;
} else {
print_default_text(writer, &table_schema)?;
}
}
Ok(())
}
fn print_default_text(writer: &mut dyn Write, schema: &TableSchema) -> Result<(), IdbError> {
if let Some(ref db) = schema.schema_name {
wprintln!(writer, "-- Table: `{}`.`{}`", db, schema.table_name)?;
} else {
wprintln!(writer, "-- Table: `{}`", schema.table_name)?;
}
if let Some(ref ver) = schema.mysql_version {
wprintln!(writer, "-- Source: SDI (MySQL {})", ver)?;
} else {
wprintln!(writer, "-- Source: SDI")?;
}
wprintln!(writer)?;
wprintln!(writer, "{}", schema.ddl)?;
Ok(())
}
fn print_verbose_text(writer: &mut dyn Write, schema: &TableSchema) -> Result<(), IdbError> {
if let Some(ref db) = schema.schema_name {
wprintln!(writer, "Schema: {}", db)?;
}
wprintln!(writer, "Table: {}", schema.table_name)?;
wprintln!(writer, "Engine: {}", schema.engine)?;
if let Some(ref fmt) = schema.row_format {
wprintln!(writer, "Format: {}", fmt)?;
}
if let Some(ref ver) = schema.mysql_version {
wprintln!(writer, "Source: SDI (MySQL {})", ver)?;
}
if let Some(ref coll) = schema.collation {
wprintln!(writer, "Collation: {}", coll)?;
}
if let Some(ref cs) = schema.charset {
wprintln!(writer, "Charset: {}", cs)?;
}
if let Some(ref comment) = schema.comment {
wprintln!(writer, "Comment: {}", comment)?;
}
wprintln!(writer)?;
wprintln!(writer, "Columns ({}):", schema.columns.len())?;
for (i, col) in schema.columns.iter().enumerate() {
let mut parts = vec![format!(
" {}. {:<16} {:<20}",
i + 1,
col.name,
col.column_type
)];
if !col.is_nullable {
parts.push("NOT NULL".to_string());
}
if col.is_auto_increment {
parts.push("AUTO_INCREMENT".to_string());
}
if let Some(ref expr) = col.generation_expression {
let kind = if col.is_virtual == Some(true) {
"VIRTUAL"
} else {
"STORED"
};
parts.push(format!("AS ({}) {}", expr, kind));
}
if col.is_invisible {
parts.push("INVISIBLE".to_string());
}
wprintln!(writer, "{}", parts.join(" "))?;
}
if !schema.indexes.is_empty() {
wprintln!(writer)?;
wprintln!(writer, "Indexes ({}):", schema.indexes.len())?;
for idx in &schema.indexes {
let cols: Vec<String> = idx
.columns
.iter()
.map(|c| {
let mut s = c.name.clone();
if let Some(len) = c.prefix_length {
s.push_str(&format!("({})", len));
}
if let Some(ref ord) = c.order {
s.push(' ');
s.push_str(ord);
}
s
})
.collect();
if idx.index_type == "PRIMARY KEY" {
wprintln!(writer, " {} ({})", idx.index_type, cols.join(", "))?;
} else {
wprintln!(
writer,
" {} {} ({})",
idx.index_type,
idx.name,
cols.join(", ")
)?;
}
}
}
if !schema.foreign_keys.is_empty() {
wprintln!(writer)?;
wprintln!(writer, "Foreign Keys ({}):", schema.foreign_keys.len())?;
for fk in &schema.foreign_keys {
wprintln!(
writer,
" {} ({}) -> {} ({})",
fk.name,
fk.columns.join(", "),
fk.referenced_table,
fk.referenced_columns.join(", ")
)?;
}
}
wprintln!(writer)?;
wprintln!(writer, "DDL:")?;
wprintln!(writer, "{}", schema.ddl)?;
Ok(())
}
fn print_inferred_text(writer: &mut dyn Write, inferred: &InferredSchema) -> Result<(), IdbError> {
wprintln!(writer, "-- Source: {}", inferred.source)?;
wprintln!(
writer,
"-- Note: Column names and types cannot be determined without SDI."
)?;
wprintln!(writer)?;
wprintln!(writer, "Record format: {}", inferred.record_format)?;
wprintln!(writer, "Indexes detected: {}", inferred.indexes.len())?;
for idx in &inferred.indexes {
let levels = if idx.max_level > 0 {
format!(", {} non-leaf level(s)", idx.max_level)
} else {
String::new()
};
wprintln!(
writer,
" Index ID {}: {} leaf page(s){}",
idx.index_id,
idx.leaf_pages,
levels
)?;
}
Ok(())
}