1use std::io::Write;
8
9use crate::cli::wprintln;
10use crate::innodb::schema::{self, InferredSchema, TableSchema};
11use crate::innodb::sdi;
12use crate::IdbError;
13
14pub struct SchemaOptions {
16 pub file: String,
18 pub verbose: bool,
20 pub json: bool,
22 pub page_size: Option<u32>,
24 pub keyring: Option<String>,
26 pub mmap: bool,
28}
29
30pub fn execute(opts: &SchemaOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
37 let mut ts = crate::cli::open_tablespace(&opts.file, opts.page_size, opts.mmap)?;
38
39 if let Some(ref keyring_path) = opts.keyring {
40 crate::cli::setup_decryption(&mut ts, keyring_path)?;
41 }
42
43 if ts.vendor_info().vendor == crate::innodb::vendor::InnoDbVendor::MariaDB {
45 let inferred = schema::infer_schema_from_pages(&mut ts)?;
46 if opts.json {
47 wprintln!(
48 writer,
49 "{}",
50 serde_json::to_string_pretty(&inferred)
51 .map_err(|e| IdbError::Parse(e.to_string()))?
52 )?;
53 } else {
54 print_inferred_text(writer, &inferred)?;
55 }
56 return Ok(());
57 }
58
59 let sdi_pages = sdi::find_sdi_pages(&mut ts)?;
61
62 if sdi_pages.is_empty() {
63 let inferred = schema::infer_schema_from_pages(&mut ts)?;
65 if opts.json {
66 wprintln!(
67 writer,
68 "{}",
69 serde_json::to_string_pretty(&inferred)
70 .map_err(|e| IdbError::Parse(e.to_string()))?
71 )?;
72 } else {
73 print_inferred_text(writer, &inferred)?;
74 }
75 return Ok(());
76 }
77
78 let records = sdi::extract_sdi_from_pages(&mut ts, &sdi_pages)?;
80
81 let table_records: Vec<_> = records.iter().filter(|r| r.sdi_type == 1).collect();
83
84 if table_records.is_empty() {
85 wprintln!(writer, "No table SDI records found in {}.", opts.file)?;
86 return Ok(());
87 }
88
89 for rec in &table_records {
90 let table_schema = schema::extract_schema_from_sdi(&rec.data)?;
91
92 if opts.json {
93 wprintln!(
94 writer,
95 "{}",
96 serde_json::to_string_pretty(&table_schema)
97 .map_err(|e| IdbError::Parse(e.to_string()))?
98 )?;
99 } else if opts.verbose {
100 print_verbose_text(writer, &table_schema)?;
101 } else {
102 print_default_text(writer, &table_schema)?;
103 }
104 }
105
106 Ok(())
107}
108
109fn print_default_text(writer: &mut dyn Write, schema: &TableSchema) -> Result<(), IdbError> {
111 if let Some(ref db) = schema.schema_name {
113 wprintln!(writer, "-- Table: `{}`.`{}`", db, schema.table_name)?;
114 } else {
115 wprintln!(writer, "-- Table: `{}`", schema.table_name)?;
116 }
117
118 if let Some(ref ver) = schema.mysql_version {
119 wprintln!(writer, "-- Source: SDI (MySQL {})", ver)?;
120 } else {
121 wprintln!(writer, "-- Source: SDI")?;
122 }
123
124 wprintln!(writer)?;
125 wprintln!(writer, "{}", schema.ddl)?;
126
127 Ok(())
128}
129
130fn print_verbose_text(writer: &mut dyn Write, schema: &TableSchema) -> Result<(), IdbError> {
132 if let Some(ref db) = schema.schema_name {
133 wprintln!(writer, "Schema: {}", db)?;
134 }
135 wprintln!(writer, "Table: {}", schema.table_name)?;
136 wprintln!(writer, "Engine: {}", schema.engine)?;
137 if let Some(ref fmt) = schema.row_format {
138 wprintln!(writer, "Format: {}", fmt)?;
139 }
140 if let Some(ref ver) = schema.mysql_version {
141 wprintln!(writer, "Source: SDI (MySQL {})", ver)?;
142 }
143 if let Some(ref coll) = schema.collation {
144 wprintln!(writer, "Collation: {}", coll)?;
145 }
146 if let Some(ref cs) = schema.charset {
147 wprintln!(writer, "Charset: {}", cs)?;
148 }
149 if let Some(ref comment) = schema.comment {
150 wprintln!(writer, "Comment: {}", comment)?;
151 }
152
153 wprintln!(writer)?;
155 wprintln!(writer, "Columns ({}):", schema.columns.len())?;
156 for (i, col) in schema.columns.iter().enumerate() {
157 let mut parts = vec![format!(
158 " {}. {:<16} {:<20}",
159 i + 1,
160 col.name,
161 col.column_type
162 )];
163 if !col.is_nullable {
164 parts.push("NOT NULL".to_string());
165 }
166 if col.is_auto_increment {
167 parts.push("AUTO_INCREMENT".to_string());
168 }
169 if let Some(ref expr) = col.generation_expression {
170 let kind = if col.is_virtual == Some(true) {
171 "VIRTUAL"
172 } else {
173 "STORED"
174 };
175 parts.push(format!("AS ({}) {}", expr, kind));
176 }
177 if col.is_invisible {
178 parts.push("INVISIBLE".to_string());
179 }
180 wprintln!(writer, "{}", parts.join(" "))?;
181 }
182
183 if !schema.indexes.is_empty() {
185 wprintln!(writer)?;
186 wprintln!(writer, "Indexes ({}):", schema.indexes.len())?;
187 for idx in &schema.indexes {
188 let cols: Vec<String> = idx
189 .columns
190 .iter()
191 .map(|c| {
192 let mut s = c.name.clone();
193 if let Some(len) = c.prefix_length {
194 s.push_str(&format!("({})", len));
195 }
196 if let Some(ref ord) = c.order {
197 s.push(' ');
198 s.push_str(ord);
199 }
200 s
201 })
202 .collect();
203 if idx.index_type == "PRIMARY KEY" {
204 wprintln!(writer, " {} ({})", idx.index_type, cols.join(", "))?;
205 } else {
206 wprintln!(
207 writer,
208 " {} {} ({})",
209 idx.index_type,
210 idx.name,
211 cols.join(", ")
212 )?;
213 }
214 }
215 }
216
217 if !schema.foreign_keys.is_empty() {
219 wprintln!(writer)?;
220 wprintln!(writer, "Foreign Keys ({}):", schema.foreign_keys.len())?;
221 for fk in &schema.foreign_keys {
222 wprintln!(
223 writer,
224 " {} ({}) -> {} ({})",
225 fk.name,
226 fk.columns.join(", "),
227 fk.referenced_table,
228 fk.referenced_columns.join(", ")
229 )?;
230 }
231 }
232
233 wprintln!(writer)?;
235 wprintln!(writer, "DDL:")?;
236 wprintln!(writer, "{}", schema.ddl)?;
237
238 Ok(())
239}
240
241fn print_inferred_text(writer: &mut dyn Write, inferred: &InferredSchema) -> Result<(), IdbError> {
243 wprintln!(writer, "-- Source: {}", inferred.source)?;
244 wprintln!(
245 writer,
246 "-- Note: Column names and types cannot be determined without SDI."
247 )?;
248 wprintln!(writer)?;
249 wprintln!(writer, "Record format: {}", inferred.record_format)?;
250 wprintln!(writer, "Indexes detected: {}", inferred.indexes.len())?;
251 for idx in &inferred.indexes {
252 let levels = if idx.max_level > 0 {
253 format!(", {} non-leaf level(s)", idx.max_level)
254 } else {
255 String::new()
256 };
257 wprintln!(
258 writer,
259 " Index ID {}: {} leaf page(s){}",
260 idx.index_id,
261 idx.leaf_pages,
262 levels
263 )?;
264 }
265
266 Ok(())
267}