1use std::io::Write;
8
9use crate::cli::wprintln;
10use crate::innodb::export::{csv_escape, decode_page_records, extract_column_layout};
11use crate::innodb::field_decode::{ColumnStorageInfo, FieldValue};
12use crate::innodb::index::IndexHeader;
13use crate::innodb::page::FilHeader;
14use crate::innodb::page_types::PageType;
15use crate::innodb::record::walk_compact_records;
16use crate::IdbError;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ExportFormat {
21 Csv,
22 Json,
23 Hex,
24}
25
26impl ExportFormat {
27 fn from_str(s: &str) -> Result<Self, IdbError> {
28 match s.to_lowercase().as_str() {
29 "csv" => Ok(ExportFormat::Csv),
30 "json" => Ok(ExportFormat::Json),
31 "hex" => Ok(ExportFormat::Hex),
32 _ => Err(IdbError::Argument(format!(
33 "Unknown format '{}'. Use csv, json, or hex.",
34 s
35 ))),
36 }
37 }
38}
39
40pub struct ExportOptions {
42 pub file: String,
44 pub page: Option<u64>,
46 pub format: String,
48 pub where_delete_mark: bool,
50 pub system_columns: bool,
52 pub verbose: bool,
54 pub page_size: Option<u32>,
56 pub keyring: Option<String>,
58 pub mmap: bool,
60}
61
62pub fn execute(opts: &ExportOptions, writer: &mut dyn Write) -> Result<(), IdbError> {
64 let format = ExportFormat::from_str(&opts.format)?;
65
66 let mut ts = crate::cli::open_tablespace(&opts.file, opts.page_size, opts.mmap)?;
67
68 if let Some(ref keyring_path) = opts.keyring {
69 crate::cli::setup_decryption(&mut ts, keyring_path)?;
70 }
71
72 let page_size = ts.page_size();
73
74 let column_layout = extract_column_layout(&mut ts);
76 let (columns, clustered_index_id) = match column_layout {
77 Some((cols, idx_id)) => (Some(cols), Some(idx_id)),
78 None => {
79 if format != ExportFormat::Hex {
80 eprintln!("Warning: No SDI metadata found. Falling back to hex output.");
81 }
82 (None, None)
83 }
84 };
85
86 let use_hex = columns.is_none() || format == ExportFormat::Hex;
87
88 let target_index_id = clustered_index_id;
91
92 let mut pages_data: Vec<(u64, Vec<u8>)> = Vec::new();
94 ts.for_each_page(|page_num, data| {
95 if let Some(specific_page) = opts.page {
96 if page_num != specific_page {
97 return Ok(());
98 }
99 }
100 let fil = match FilHeader::parse(data) {
101 Some(h) => h,
102 None => return Ok(()),
103 };
104 if fil.page_type != PageType::Index {
105 return Ok(());
106 }
107 let idx = match IndexHeader::parse(data) {
108 Some(h) => h,
109 None => return Ok(()),
110 };
111 if !idx.is_leaf() {
113 return Ok(());
114 }
115 if let Some(target_id) = target_index_id {
117 if idx.index_id != target_id {
118 return Ok(());
119 }
120 }
121 pages_data.push((page_num, data.to_vec()));
122 Ok(())
123 })?;
124
125 if use_hex {
126 output_hex(writer, &pages_data, opts)?;
127 } else {
128 let cols = columns.as_ref().unwrap();
129 match format {
130 ExportFormat::Csv => output_csv(writer, &pages_data, cols, opts, page_size)?,
131 ExportFormat::Json => output_json(writer, &pages_data, cols, opts, page_size)?,
132 ExportFormat::Hex => unreachable!(),
133 }
134 }
135
136 Ok(())
137}
138
139fn output_csv(
141 writer: &mut dyn Write,
142 pages: &[(u64, Vec<u8>)],
143 columns: &[ColumnStorageInfo],
144 opts: &ExportOptions,
145 page_size: u32,
146) -> Result<(), IdbError> {
147 let headers: Vec<&str> = columns
149 .iter()
150 .filter(|c| opts.system_columns || !c.is_system_column)
151 .map(|c| c.name.as_str())
152 .collect();
153 wprintln!(writer, "{}", headers.join(","))?;
154
155 for (_, page_data) in pages {
156 let rows = decode_page_records(
157 page_data,
158 columns,
159 opts.where_delete_mark,
160 opts.system_columns,
161 page_size,
162 );
163 for row in &rows {
164 let values: Vec<String> = row.iter().map(|(_, v)| csv_escape(v)).collect();
165 wprintln!(writer, "{}", values.join(","))?;
166 }
167 }
168
169 Ok(())
170}
171
172fn output_json(
174 writer: &mut dyn Write,
175 pages: &[(u64, Vec<u8>)],
176 columns: &[ColumnStorageInfo],
177 opts: &ExportOptions,
178 page_size: u32,
179) -> Result<(), IdbError> {
180 let mut all_rows: Vec<serde_json::Map<String, serde_json::Value>> = Vec::new();
181
182 for (_, page_data) in pages {
183 let rows = decode_page_records(
184 page_data,
185 columns,
186 opts.where_delete_mark,
187 opts.system_columns,
188 page_size,
189 );
190 for row in rows {
191 let mut obj = serde_json::Map::new();
192 for (name, val) in row {
193 let json_val = match val {
194 FieldValue::Null => serde_json::Value::Null,
195 FieldValue::Int(n) => serde_json::Value::Number(n.into()),
196 FieldValue::Uint(n) => serde_json::Value::Number(n.into()),
197 FieldValue::Float(f) => serde_json::Number::from_f64(f as f64)
198 .map(serde_json::Value::Number)
199 .unwrap_or(serde_json::Value::Null),
200 FieldValue::Double(d) => serde_json::Number::from_f64(d)
201 .map(serde_json::Value::Number)
202 .unwrap_or(serde_json::Value::Null),
203 FieldValue::Str(s) => serde_json::Value::String(s),
204 FieldValue::Hex(h) => serde_json::Value::String(h),
205 };
206 obj.insert(name, json_val);
207 }
208 all_rows.push(obj);
209 }
210 }
211
212 let json_output =
213 serde_json::to_string_pretty(&all_rows).map_err(|e| IdbError::Parse(e.to_string()))?;
214 wprintln!(writer, "{}", json_output)?;
215
216 Ok(())
217}
218
219fn output_hex(
221 writer: &mut dyn Write,
222 pages: &[(u64, Vec<u8>)],
223 opts: &ExportOptions,
224) -> Result<(), IdbError> {
225 wprintln!(
226 writer,
227 "{:<8} {:<8} {:<8} {:<6} {}",
228 "PAGE",
229 "OFFSET",
230 "HEAP_NO",
231 "DEL",
232 "DATA (hex)"
233 )?;
234
235 for (page_num, page_data) in pages {
236 let records = walk_compact_records(page_data);
237 for rec in &records {
238 let delete_mark = rec.header.delete_mark();
239 if opts.where_delete_mark && !delete_mark {
240 continue;
241 }
242 if !opts.where_delete_mark && delete_mark {
243 continue;
244 }
245
246 let heap_no = rec.header.heap_no();
247 let data_end = (rec.offset + 64).min(page_data.len());
249 let data_hex: String = page_data[rec.offset..data_end]
250 .iter()
251 .map(|b| format!("{:02x}", b))
252 .collect();
253
254 wprintln!(
255 writer,
256 "{:<8} {:<8} {:<8} {:<6} {}",
257 page_num,
258 rec.offset,
259 heap_no,
260 if delete_mark { "Y" } else { "N" },
261 data_hex
262 )?;
263 }
264 }
265
266 Ok(())
267}