idb/cli/app.rs
1use clap::{Parser, Subcommand, ValueEnum};
2
3/// Top-level CLI definition for the `inno` binary.
4#[derive(Parser)]
5#[command(name = "inno")]
6#[command(about = "InnoDB file analysis toolkit")]
7#[command(version)]
8pub struct Cli {
9 /// Control colored output
10 #[arg(long, default_value = "auto", global = true)]
11 pub color: ColorMode,
12
13 /// Write output to a file instead of stdout
14 #[arg(short, long, global = true)]
15 pub output: Option<String>,
16
17 /// Number of threads for parallel page processing (0 = auto-detect CPU count)
18 #[arg(long, default_value = "0", global = true)]
19 pub threads: usize,
20
21 /// Use memory-mapped I/O for file access (can be faster for large files)
22 #[arg(long, global = true)]
23 pub mmap: bool,
24
25 /// Output format (text, json, or csv); overrides per-subcommand --json
26 #[arg(long, global = true, default_value = "text")]
27 pub format: OutputFormat,
28
29 /// Write NDJSON audit events to a file for all write operations
30 #[arg(long = "audit-log", global = true)]
31 pub audit_log: Option<String>,
32
33 #[command(subcommand)]
34 pub command: Commands,
35}
36
37/// Controls when colored output is emitted.
38#[derive(Clone, Copy, ValueEnum)]
39pub enum ColorMode {
40 Auto,
41 Always,
42 Never,
43}
44
45/// Global output format selection.
46#[derive(Clone, Copy, PartialEq, Eq, ValueEnum)]
47pub enum OutputFormat {
48 Text,
49 Json,
50 Csv,
51}
52
53/// Available subcommands for the `inno` CLI.
54#[derive(Subcommand)]
55pub enum Commands {
56 /// Parse .ibd file and display page summary
57 ///
58 /// Reads the 38-byte FIL header of every page in a tablespace, decodes the
59 /// page type, checksum, LSN, prev/next pointers, and space ID, then prints
60 /// a per-page breakdown followed by a page-type frequency summary table.
61 /// Page 0 additionally shows the FSP header (space ID, size, flags).
62 /// Use `--no-empty` to skip zero-checksum allocated pages, or `-p` to
63 /// inspect a single page in detail. With `--verbose`, checksum validation
64 /// and LSN consistency results are included for each page.
65 Parse {
66 /// Path to InnoDB data file (.ibd)
67 #[arg(short, long)]
68 file: String,
69
70 /// Display a specific page number
71 #[arg(short, long)]
72 page: Option<u64>,
73
74 /// Display additional information
75 #[arg(short, long)]
76 verbose: bool,
77
78 /// Skip empty/allocated pages
79 #[arg(short = 'e', long = "no-empty")]
80 no_empty: bool,
81
82 /// Output in JSON format
83 #[arg(long)]
84 json: bool,
85
86 /// Override page size (default: auto-detect)
87 #[arg(long = "page-size")]
88 page_size: Option<u32>,
89
90 /// Path to MySQL keyring file for decrypting encrypted tablespaces
91 #[arg(long)]
92 keyring: Option<String>,
93
94 /// Stream results incrementally for lower memory usage (disables parallel processing)
95 #[arg(long)]
96 streaming: bool,
97 },
98
99 /// Detailed page structure analysis
100 ///
101 /// Goes beyond FIL headers to decode the internal structure of each page
102 /// type: INDEX pages show the B+Tree index header, FSEG inode pointers, and
103 /// infimum/supremum system records; UNDO pages show the undo page header
104 /// and segment state; BLOB/LOB pages show chain pointers and data lengths;
105 /// and page 0 shows extended FSP header fields including compression and
106 /// encryption flags. Use `-l` for a compact one-line-per-page listing,
107 /// `-t INDEX` to filter by page type, or `-p` for a single page deep dive.
108 Pages {
109 /// Path to InnoDB data file (.ibd)
110 #[arg(short, long)]
111 file: String,
112
113 /// Display a specific page number
114 #[arg(short, long)]
115 page: Option<u64>,
116
117 /// Display additional information
118 #[arg(short, long)]
119 verbose: bool,
120
121 /// Show empty/allocated pages
122 #[arg(short = 'e', long = "show-empty")]
123 show_empty: bool,
124
125 /// Compact list mode (one line per page)
126 #[arg(short, long)]
127 list: bool,
128
129 /// Filter by page type (e.g., INDEX)
130 #[arg(short = 't', long = "type")]
131 filter_type: Option<String>,
132
133 /// Show delete-marked record statistics for INDEX pages
134 #[arg(long)]
135 deleted: bool,
136
137 /// Output in JSON format
138 #[arg(long)]
139 json: bool,
140
141 /// Override page size (default: auto-detect)
142 #[arg(long = "page-size")]
143 page_size: Option<u32>,
144
145 /// Path to MySQL keyring file for decrypting encrypted tablespaces
146 #[arg(long)]
147 keyring: Option<String>,
148 },
149
150 /// Hex dump of raw page bytes
151 ///
152 /// Operates in two modes: **page mode** (default) reads a full page by
153 /// number and produces a formatted hex dump with file-relative offsets;
154 /// **offset mode** (`--offset`) reads bytes at an arbitrary file position,
155 /// useful for inspecting structures that cross page boundaries. Use
156 /// `--length` to limit the number of bytes shown, or `--raw` to emit
157 /// unformatted binary bytes suitable for piping to other tools.
158 Dump {
159 /// Path to InnoDB data file
160 #[arg(short, long)]
161 file: String,
162
163 /// Page number to dump (default: 0)
164 #[arg(short, long)]
165 page: Option<u64>,
166
167 /// Absolute byte offset to start dumping (bypasses page mode)
168 #[arg(long)]
169 offset: Option<u64>,
170
171 /// Number of bytes to dump (default: page size or 256 for offset mode)
172 #[arg(short, long)]
173 length: Option<usize>,
174
175 /// Output raw binary bytes (no formatting)
176 #[arg(long)]
177 raw: bool,
178
179 /// Override page size (default: auto-detect)
180 #[arg(long = "page-size")]
181 page_size: Option<u32>,
182
183 /// Path to MySQL keyring file for decrypting encrypted tablespaces
184 #[arg(long)]
185 keyring: Option<String>,
186
187 /// Decrypt page before dumping (requires --keyring)
188 #[arg(long)]
189 decrypt: bool,
190 },
191
192 /// Intentionally corrupt pages for testing
193 ///
194 /// Writes random bytes into a tablespace file to simulate data corruption.
195 /// Targets can be the FIL header (`-k`), the record data area (`-r`), or
196 /// an absolute byte offset (`--offset`). If no page is specified, one is
197 /// chosen at random. Use `--verify` to print before/after checksum
198 /// comparisons confirming the page is now invalid — useful for verifying
199 /// that `inno checksum` correctly detects the damage.
200 Corrupt {
201 /// Path to data file
202 #[arg(short, long)]
203 file: String,
204
205 /// Page number to corrupt (random if not specified)
206 #[arg(short, long)]
207 page: Option<u64>,
208
209 /// Number of bytes to corrupt
210 #[arg(short, long, default_value = "1")]
211 bytes: usize,
212
213 /// Corrupt the FIL header area
214 #[arg(short = 'k', long = "header")]
215 header: bool,
216
217 /// Corrupt the record data area
218 #[arg(short, long)]
219 records: bool,
220
221 /// Absolute byte offset to corrupt (bypasses page calculation)
222 #[arg(long)]
223 offset: Option<u64>,
224
225 /// Show before/after checksum comparison
226 #[arg(long)]
227 verify: bool,
228
229 /// Output in JSON format
230 #[arg(long)]
231 json: bool,
232
233 /// Override page size (default: auto-detect)
234 #[arg(long = "page-size")]
235 page_size: Option<u32>,
236 },
237
238 /// Export record-level data from a tablespace
239 ///
240 /// Extracts user records from clustered index leaf pages and outputs
241 /// them as CSV, JSON, or raw hex. Uses SDI metadata (MySQL 8.0+) to
242 /// decode field types and column names. Without SDI, falls back to
243 /// hex-only output.
244 ///
245 /// Supported types: integers (TINYINT–BIGINT), FLOAT, DOUBLE,
246 /// DATE, DATETIME, TIMESTAMP, YEAR, VARCHAR, CHAR. Unsupported
247 /// types (DECIMAL, BLOB, JSON, etc.) are exported as hex strings.
248 ///
249 /// Use `--where-delete-mark` to include only delete-marked records
250 /// (useful for forensic recovery). Use `--system-columns` to include
251 /// DB_TRX_ID and DB_ROLL_PTR in the output.
252 Export {
253 /// Path to InnoDB data file (.ibd)
254 #[arg(short, long)]
255 file: String,
256
257 /// Export records from a specific page only
258 #[arg(short, long)]
259 page: Option<u64>,
260
261 /// Output format: csv, json, or hex
262 #[arg(long, default_value = "csv")]
263 format: String,
264
265 /// Include only delete-marked records
266 #[arg(long = "where-delete-mark")]
267 where_delete_mark: bool,
268
269 /// Include system columns (DB_TRX_ID, DB_ROLL_PTR) in output
270 #[arg(long = "system-columns")]
271 system_columns: bool,
272
273 /// Show additional details
274 #[arg(short, long)]
275 verbose: bool,
276
277 /// Override page size (default: auto-detect)
278 #[arg(long = "page-size")]
279 page_size: Option<u32>,
280
281 /// Path to MySQL keyring file for decrypting encrypted tablespaces
282 #[arg(long)]
283 keyring: Option<String>,
284 },
285
286 /// Search for pages across data directory
287 ///
288 /// Recursively discovers all `.ibd` files under a MySQL data directory,
289 /// opens each as a tablespace, and reads the FIL header of every page
290 /// looking for a matching `page_number` field. Optional `--checksum` and
291 /// `--space-id` filters narrow results when the same page number appears
292 /// in multiple tablespaces. Use `--first` to stop after the first match
293 /// for faster lookups.
294 ///
295 /// With `--corrupt`, scans all pages for checksum mismatches instead of
296 /// searching by page number. Reports corrupt pages with their stored and
297 /// calculated checksums plus corruption pattern classification.
298 Find {
299 /// MySQL data directory path
300 #[arg(short, long)]
301 datadir: String,
302
303 /// Page number to search for
304 #[arg(short, long)]
305 page: Option<u64>,
306
307 /// Checksum to match (page-number search only)
308 #[arg(short, long)]
309 checksum: Option<u32>,
310
311 /// Space ID to match
312 #[arg(short, long)]
313 space_id: Option<u32>,
314
315 /// Scan for pages with checksum mismatches
316 #[arg(long)]
317 corrupt: bool,
318
319 /// Stop at first match
320 #[arg(long)]
321 first: bool,
322
323 /// Output in JSON format
324 #[arg(long)]
325 json: bool,
326
327 /// Override page size (default: auto-detect)
328 #[arg(long = "page-size")]
329 page_size: Option<u32>,
330
331 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
332 #[arg(long)]
333 depth: Option<u32>,
334 },
335
336 /// List/find tablespace IDs
337 ///
338 /// Scans `.ibd` and `.ibu` files under a MySQL data directory and reads
339 /// the space ID from the FSP header (page 0, offset 38) of each file.
340 /// In **list mode** (`-l`) it prints every file and its space ID; in
341 /// **lookup mode** (`-t <id>`) it finds the file that owns a specific
342 /// tablespace ID. Useful for mapping a space ID seen in error logs or
343 /// `INFORMATION_SCHEMA` back to a physical file on disk.
344 Tsid {
345 /// MySQL data directory path
346 #[arg(short, long)]
347 datadir: String,
348
349 /// List all tablespace IDs
350 #[arg(short, long)]
351 list: bool,
352
353 /// Find table file by tablespace ID
354 #[arg(short = 't', long = "tsid")]
355 tablespace_id: Option<u32>,
356
357 /// Output in JSON format
358 #[arg(long)]
359 json: bool,
360
361 /// Override page size (default: auto-detect)
362 #[arg(long = "page-size")]
363 page_size: Option<u32>,
364
365 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
366 #[arg(long)]
367 depth: Option<u32>,
368 },
369
370 /// Extract SDI metadata (MySQL 8.0+)
371 ///
372 /// Locates SDI (Serialized Dictionary Information) pages in a tablespace
373 /// by scanning for page type 17853, then reassembles multi-page SDI
374 /// records by following the page chain. The zlib-compressed payload is
375 /// decompressed and printed as JSON. Each tablespace in MySQL 8.0+
376 /// embeds its own table/column/index definitions as SDI records,
377 /// eliminating the need for the `.frm` files used in older versions.
378 /// Use `--pretty` for indented JSON output.
379 Sdi {
380 /// Path to InnoDB data file (.ibd)
381 #[arg(short, long)]
382 file: String,
383
384 /// Pretty-print JSON output
385 #[arg(short, long)]
386 pretty: bool,
387
388 /// Override page size (default: auto-detect)
389 #[arg(long = "page-size")]
390 page_size: Option<u32>,
391
392 /// Path to MySQL keyring file for decrypting encrypted tablespaces
393 #[arg(long)]
394 keyring: Option<String>,
395 },
396
397 /// Extract schema and reconstruct DDL from tablespace metadata
398 ///
399 /// Reads SDI (Serialized Dictionary Information) from MySQL 8.0+
400 /// tablespaces, parses the embedded data dictionary JSON into typed
401 /// column, index, and foreign key definitions, and reconstructs a
402 /// complete `CREATE TABLE` DDL statement. For pre-8.0 tablespaces
403 /// that lack SDI, scans INDEX pages to infer basic index structure
404 /// and record format (compact vs. redundant).
405 ///
406 /// Use `--verbose` for a structured breakdown of columns, indexes,
407 /// and foreign keys above the DDL. Use `--json` for machine-readable
408 /// output including the full schema definition and DDL as a JSON object.
409 Schema {
410 /// Path to InnoDB data file (.ibd)
411 #[arg(short, long)]
412 file: String,
413
414 /// Show structured schema breakdown above the DDL
415 #[arg(short, long)]
416 verbose: bool,
417
418 /// Output in JSON format
419 #[arg(long)]
420 json: bool,
421
422 /// Override page size (default: auto-detect)
423 #[arg(long = "page-size")]
424 page_size: Option<u32>,
425
426 /// Path to MySQL keyring file for decrypting encrypted tablespaces
427 #[arg(long)]
428 keyring: Option<String>,
429 },
430
431 /// Analyze InnoDB redo log files
432 ///
433 /// Opens an InnoDB redo log file (`ib_logfile0`/`ib_logfile1` for
434 /// MySQL < 8.0.30, or `#ib_redo*` files for 8.0.30+) and displays
435 /// the log file header, both checkpoint records, and per-block details
436 /// including block number, data length, checkpoint number, and CRC-32C
437 /// checksum status. With `--verbose`, MLOG record types within each
438 /// data block are decoded and summarized. Use `--blocks N` to limit
439 /// output to the first N data blocks, or `--no-empty` to skip blocks
440 /// that contain no redo data.
441 Log {
442 /// Path to redo log file (ib_logfile0, ib_logfile1, or #ib_redo*)
443 #[arg(short, long)]
444 file: String,
445
446 /// Limit to first N data blocks
447 #[arg(short, long)]
448 blocks: Option<u64>,
449
450 /// Skip empty blocks
451 #[arg(long)]
452 no_empty: bool,
453
454 /// Display additional information
455 #[arg(short, long)]
456 verbose: bool,
457
458 /// Output in JSON format
459 #[arg(long)]
460 json: bool,
461 },
462
463 /// Show InnoDB file and system information
464 ///
465 /// Operates in three modes. **`--ibdata`** reads the `ibdata1` page 0
466 /// FIL header and redo log checkpoint LSNs. **`--lsn-check`** compares
467 /// the `ibdata1` header LSN with the latest redo log checkpoint LSN to
468 /// detect whether the system tablespace and redo log are in sync (useful
469 /// for diagnosing crash-recovery state). **`-D`/`-t`** queries a live
470 /// MySQL instance via `INFORMATION_SCHEMA.INNODB_TABLES` and
471 /// `INNODB_INDEXES` for tablespace IDs, table IDs, index root pages,
472 /// and key InnoDB status metrics (requires the `mysql` feature).
473 Info {
474 /// Inspect ibdata1 page 0 header
475 #[arg(long)]
476 ibdata: bool,
477
478 /// Compare ibdata1 and redo log LSNs
479 #[arg(long = "lsn-check")]
480 lsn_check: bool,
481
482 /// MySQL data directory path
483 #[arg(short, long)]
484 datadir: Option<String>,
485
486 /// Database name (for table/index info)
487 #[arg(short = 'D', long)]
488 database: Option<String>,
489
490 /// Table name (for table/index info)
491 #[arg(short, long)]
492 table: Option<String>,
493
494 /// MySQL host
495 #[arg(long)]
496 host: Option<String>,
497
498 /// MySQL port
499 #[arg(long)]
500 port: Option<u16>,
501
502 /// MySQL user
503 #[arg(long)]
504 user: Option<String>,
505
506 /// MySQL password
507 #[arg(long)]
508 password: Option<String>,
509
510 /// Path to MySQL defaults file (.my.cnf)
511 #[arg(long = "defaults-file")]
512 defaults_file: Option<String>,
513
514 /// Scan data directory and produce a tablespace ID mapping
515 #[arg(long = "tablespace-map")]
516 tablespace_map: bool,
517
518 /// Output in JSON format
519 #[arg(long)]
520 json: bool,
521
522 /// Override page size (default: auto-detect)
523 #[arg(long = "page-size")]
524 page_size: Option<u32>,
525 },
526
527 /// Recover data from corrupt/damaged tablespace files
528 ///
529 /// Scans a tablespace file and classifies each page as intact, corrupt,
530 /// empty, or unreadable. For INDEX pages, counts recoverable user records
531 /// by walking the compact record chain. Produces a recovery assessment
532 /// showing how many pages and records can be salvaged.
533 ///
534 /// Use `--force` to also extract records from pages with bad checksums
535 /// but valid-looking headers — useful when data is partially damaged
536 /// but the record chain is still intact. Use `--page-size` to override
537 /// page size detection when page 0 is corrupt.
538 ///
539 /// With `--verbose`, per-page details are shown including page type,
540 /// status, LSN, and record count. With `--json`, a structured report
541 /// is emitted including optional per-record detail when combined with
542 /// `--verbose`.
543 Recover {
544 /// Path to InnoDB data file (.ibd)
545 #[arg(short, long)]
546 file: String,
547
548 /// Analyze a single page instead of full scan
549 #[arg(short, long)]
550 page: Option<u64>,
551
552 /// Show per-page details
553 #[arg(short, long)]
554 verbose: bool,
555
556 /// Output in JSON format
557 #[arg(long)]
558 json: bool,
559
560 /// Extract records from corrupt pages with valid headers
561 #[arg(long)]
562 force: bool,
563
564 /// Override page size (critical when page 0 is corrupt)
565 #[arg(long = "page-size")]
566 page_size: Option<u32>,
567
568 /// Path to MySQL keyring file for decrypting encrypted tablespaces
569 #[arg(long)]
570 keyring: Option<String>,
571
572 /// Stream results incrementally for lower memory usage (disables parallel processing)
573 #[arg(long)]
574 streaming: bool,
575
576 /// Write a new tablespace from recoverable pages to the given path
577 #[arg(long)]
578 rebuild: Option<String>,
579 },
580
581 /// Validate page checksums
582 ///
583 /// Reads every page in a tablespace and validates its stored checksum
584 /// against both CRC-32C (MySQL 5.7.7+) and legacy InnoDB algorithms.
585 /// Also checks that the header LSN low-32 bits match the FIL trailer.
586 /// All-zero pages are counted as empty and skipped. With `--verbose`,
587 /// per-page results are printed including the detected algorithm and
588 /// stored vs. calculated values. Exits with code 1 if any page has an
589 /// invalid checksum, making it suitable for use in scripts and CI.
590 Checksum {
591 /// Path to InnoDB data file (.ibd)
592 #[arg(short, long)]
593 file: String,
594
595 /// Show per-page checksum details
596 #[arg(short, long)]
597 verbose: bool,
598
599 /// Output in JSON format
600 #[arg(long)]
601 json: bool,
602
603 /// Override page size (default: auto-detect)
604 #[arg(long = "page-size")]
605 page_size: Option<u32>,
606
607 /// Path to MySQL keyring file for decrypting encrypted tablespaces
608 #[arg(long)]
609 keyring: Option<String>,
610
611 /// Stream results incrementally for lower memory usage (disables parallel processing)
612 #[arg(long)]
613 streaming: bool,
614 },
615
616 /// Monitor a tablespace file for page-level changes
617 ///
618 /// Polls an InnoDB tablespace file at a configurable interval and reports
619 /// which pages have been modified, added, or removed since the last poll.
620 /// Change detection is based on LSN comparison — if a page's LSN changes
621 /// between polls, it was modified by a write. Checksums are validated for
622 /// each changed page to detect corruption during writes.
623 ///
624 /// The tablespace is re-opened each cycle to detect file growth and avoid
625 /// stale file handles. Use `--verbose` for per-field diffs on changed
626 /// pages, or `--json` for NDJSON streaming output (one JSON object per
627 /// line). Press Ctrl+C for a clean exit with a summary of total changes.
628 Watch {
629 /// Path to InnoDB data file (.ibd)
630 #[arg(short, long)]
631 file: String,
632
633 /// Polling interval in milliseconds
634 #[arg(short, long, default_value = "1000")]
635 interval: u64,
636
637 /// Show per-field diffs for changed pages
638 #[arg(short, long)]
639 verbose: bool,
640
641 /// Output in NDJSON streaming format
642 #[arg(long)]
643 json: bool,
644
645 /// Emit per-page NDJSON change events (audit-log compatible)
646 #[arg(long)]
647 events: bool,
648
649 /// Override page size (default: auto-detect)
650 #[arg(long = "page-size")]
651 page_size: Option<u32>,
652
653 /// Path to MySQL keyring file for decrypting encrypted tablespaces
654 #[arg(long)]
655 keyring: Option<String>,
656 },
657
658 /// Repair corrupt page checksums
659 ///
660 /// Recalculates and writes correct checksums for pages with invalid checksums
661 /// or LSN mismatches. By default, auto-detects the checksum algorithm from
662 /// page 0 and creates a `.bak` backup before modifying the file. Use
663 /// `--algorithm` to force a specific algorithm, `--dry-run` to preview
664 /// repairs without modifying the file, or `--no-backup` to skip the backup.
665 Repair {
666 /// Path to InnoDB data file (.ibd)
667 #[arg(short, long)]
668 file: Option<String>,
669
670 /// Repair all .ibd files under a data directory
671 #[arg(long)]
672 batch: Option<String>,
673
674 /// Repair only a specific page number
675 #[arg(short, long)]
676 page: Option<u64>,
677
678 /// Checksum algorithm: auto, crc32c, innodb, full_crc32
679 #[arg(short, long, default_value = "auto")]
680 algorithm: String,
681
682 /// Skip creating a backup before repair
683 #[arg(long)]
684 no_backup: bool,
685
686 /// Preview repairs without modifying the file
687 #[arg(long)]
688 dry_run: bool,
689
690 /// Show per-page repair details
691 #[arg(short, long)]
692 verbose: bool,
693
694 /// Output in JSON format
695 #[arg(long)]
696 json: bool,
697
698 /// Override page size (default: auto-detect)
699 #[arg(long = "page-size")]
700 page_size: Option<u32>,
701
702 /// Path to MySQL keyring file for decrypting encrypted tablespaces
703 #[arg(long)]
704 keyring: Option<String>,
705 },
706
707 /// Compare two tablespace files page-by-page
708 ///
709 /// Reads two InnoDB tablespace files and compares them page-by-page,
710 /// reporting which pages are identical, modified, or only present in
711 /// one file. With `--verbose`, per-page FIL header field diffs are
712 /// shown for modified pages, highlighting changes to checksums, LSNs,
713 /// page types, and space IDs. Add `--byte-ranges` (with `-v`) to see
714 /// the exact byte offsets where page content differs. Use `-p` to
715 /// compare a single page, or `--json` for machine-readable output.
716 ///
717 /// When files have different page sizes, only FIL headers (first 38
718 /// bytes) are compared and a warning is displayed.
719 Diff {
720 /// First InnoDB data file (.ibd)
721 file1: String,
722
723 /// Second InnoDB data file (.ibd)
724 file2: String,
725
726 /// Show per-page header field diffs
727 #[arg(short, long)]
728 verbose: bool,
729
730 /// Show byte-range diffs for changed pages (requires -v)
731 #[arg(short = 'b', long = "byte-ranges")]
732 byte_ranges: bool,
733
734 /// Compare a single page only
735 #[arg(short, long)]
736 page: Option<u64>,
737
738 /// Annotate diff with MySQL version information from SDI metadata
739 #[arg(long = "version-aware")]
740 version_aware: bool,
741
742 /// Output in JSON format
743 #[arg(long)]
744 json: bool,
745
746 /// Override page size (default: auto-detect)
747 #[arg(long = "page-size")]
748 page_size: Option<u32>,
749
750 /// Path to MySQL keyring file for decrypting encrypted tablespaces
751 #[arg(long)]
752 keyring: Option<String>,
753 },
754
755 /// Copy specific pages from a donor tablespace into a target
756 ///
757 /// Reads pages from the donor file and writes them into the target file at
758 /// the same page number. Safety checks ensure page sizes and space IDs
759 /// match. Page 0 (FSP_HDR) is rejected unless `--force` is used. Donor
760 /// pages with invalid checksums are skipped unless `--force` is used.
761 ///
762 /// A backup of the target is created by default. Use `--dry-run` to preview
763 /// which pages would be transplanted without modifying the target.
764 Transplant {
765 /// Path to donor tablespace file (source of pages)
766 donor: String,
767
768 /// Path to target tablespace file (destination)
769 target: String,
770
771 /// Page numbers to transplant (comma-separated)
772 #[arg(short, long, value_delimiter = ',')]
773 pages: Vec<u64>,
774
775 /// Skip creating a backup of the target
776 #[arg(long)]
777 no_backup: bool,
778
779 /// Allow space ID mismatch, corrupt donor pages, and page 0 transplant
780 #[arg(long)]
781 force: bool,
782
783 /// Preview without modifying the target file
784 #[arg(long)]
785 dry_run: bool,
786
787 /// Show per-page details
788 #[arg(short, long)]
789 verbose: bool,
790
791 /// Output in JSON format
792 #[arg(long)]
793 json: bool,
794
795 /// Override page size (default: auto-detect)
796 #[arg(long = "page-size")]
797 page_size: Option<u32>,
798
799 /// Path to MySQL keyring file for decrypting encrypted tablespaces
800 #[arg(long)]
801 keyring: Option<String>,
802 },
803
804 /// Per-index B+Tree health metrics
805 ///
806 /// Scans all INDEX pages in a tablespace and computes per-index health
807 /// metrics including fill factor (average, min, max), garbage ratio,
808 /// fragmentation, tree depth, and page counts. Optionally resolves
809 /// index names from SDI metadata (MySQL 8.0+).
810 ///
811 /// Use `--verbose` for additional detail including total records and
812 /// empty leaf page counts. Use `--json` for machine-readable output.
813 Health {
814 /// Path to InnoDB data file (.ibd)
815 #[arg(short, long)]
816 file: String,
817
818 /// Show additional detail (records, empty leaves)
819 #[arg(short, long)]
820 verbose: bool,
821
822 /// Output in JSON format
823 #[arg(long, conflicts_with = "prometheus")]
824 json: bool,
825
826 /// Output metrics in Prometheus exposition format
827 #[arg(long, conflicts_with = "json")]
828 prometheus: bool,
829
830 /// Override page size (default: auto-detect)
831 #[arg(long = "page-size")]
832 page_size: Option<u32>,
833
834 /// Path to MySQL keyring file for decrypting encrypted tablespaces
835 #[arg(long)]
836 keyring: Option<String>,
837 },
838
839 /// Audit a MySQL data directory for integrity, health, or corruption
840 ///
841 /// Scans all `.ibd` files under a data directory and validates checksums,
842 /// computes health metrics, or lists corrupt pages — replacing the need
843 /// to run `inno checksum` or `inno health` file-by-file. Three modes:
844 ///
845 /// **Default (integrity)**: validates checksums across all tablespace files
846 /// and reports per-file pass/fail with a directory-wide integrity percentage.
847 ///
848 /// **`--health`**: computes per-tablespace fill factor, fragmentation, and
849 /// garbage ratio, ranked worst-first. Use `--min-fill-factor` and
850 /// `--max-fragmentation` to filter for unhealthy tablespaces only.
851 ///
852 /// **`--checksum-mismatch`**: compact listing of only corrupt pages with
853 /// stored vs. calculated checksums, suitable for piping to `inno repair`.
854 ///
855 /// `--health` and `--checksum-mismatch` are mutually exclusive.
856 Audit {
857 /// MySQL data directory path
858 #[arg(short, long)]
859 datadir: String,
860
861 /// Show per-tablespace health metrics instead of checksum validation
862 #[arg(long)]
863 health: bool,
864
865 /// List only pages with checksum mismatches (compact output)
866 #[arg(long = "checksum-mismatch", conflicts_with = "prometheus")]
867 checksum_mismatch: bool,
868
869 /// Show additional details (per-page results in default mode)
870 #[arg(short, long)]
871 verbose: bool,
872
873 /// Output in JSON format
874 #[arg(long, conflicts_with = "prometheus")]
875 json: bool,
876
877 /// Output metrics in Prometheus exposition format
878 #[arg(long, conflicts_with = "json", conflicts_with = "checksum_mismatch")]
879 prometheus: bool,
880
881 /// Override page size (default: auto-detect per file)
882 #[arg(long = "page-size")]
883 page_size: Option<u32>,
884
885 /// Path to MySQL keyring file for decrypting encrypted tablespaces
886 #[arg(long)]
887 keyring: Option<String>,
888
889 /// Show tables with fill factor below this threshold (0-100, --health only)
890 #[arg(long = "min-fill-factor")]
891 min_fill_factor: Option<f64>,
892
893 /// Show tables with fragmentation above this threshold (0-100, --health only)
894 #[arg(long = "max-fragmentation")]
895 max_fragmentation: Option<f64>,
896
897 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
898 #[arg(long)]
899 depth: Option<u32>,
900 },
901
902 /// Check tablespace compatibility with a target MySQL version
903 ///
904 /// Analyzes tablespace files to determine whether they are compatible
905 /// with a target MySQL version. Checks include page size support, row
906 /// format, encryption, compression, SDI presence, and vendor
907 /// compatibility (MariaDB tablespaces are flagged as incompatible).
908 ///
909 /// **Single-file mode** (`--file`): checks one tablespace and reports
910 /// all compatibility findings with severity levels (info/warning/error).
911 ///
912 /// **Directory scan mode** (`--scan`): discovers all `.ibd` files under
913 /// a data directory, runs compatibility checks on each in parallel, and
914 /// produces a per-file summary plus an overall compatible/incompatible
915 /// count.
916 ///
917 /// `--file` and `--scan` are mutually exclusive.
918 Compat {
919 /// Path to InnoDB data file (.ibd)
920 #[arg(short, long)]
921 file: Option<String>,
922
923 /// Scan a data directory for compatibility issues (mutually exclusive with --file)
924 #[arg(short, long)]
925 scan: Option<String>,
926
927 /// Target MySQL version (e.g., "8.4.0", "9.0.0")
928 #[arg(short, long)]
929 target: String,
930
931 /// Show detailed check information
932 #[arg(short, long)]
933 verbose: bool,
934
935 /// Output in JSON format
936 #[arg(long)]
937 json: bool,
938
939 /// Override page size (default: auto-detect)
940 #[arg(long = "page-size")]
941 page_size: Option<u32>,
942
943 /// Path to MySQL keyring file for decrypting encrypted tablespaces
944 #[arg(long)]
945 keyring: Option<String>,
946
947 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
948 #[arg(long)]
949 depth: Option<u32>,
950 },
951
952 /// Defragment a tablespace by reclaiming free space and reordering pages
953 ///
954 /// Reads all pages from a source tablespace, removes empty and corrupt
955 /// pages, sorts INDEX pages by (index_id, level, page_number), fixes
956 /// prev/next chain pointers within each index group, renumbers pages
957 /// sequentially, rebuilds page 0, recalculates all checksums, and writes
958 /// the result to a new output file. The source file is never modified.
959 Defrag {
960 /// Path to source InnoDB data file (.ibd)
961 #[arg(short, long)]
962 file: String,
963
964 /// Path to output file (required — always writes a new file)
965 #[arg(short, long)]
966 output: String,
967
968 /// Show per-page details
969 #[arg(short, long)]
970 verbose: bool,
971
972 /// Output in JSON format
973 #[arg(long)]
974 json: bool,
975
976 /// Override page size (default: auto-detect)
977 #[arg(long = "page-size")]
978 page_size: Option<u32>,
979
980 /// Path to MySQL keyring file for decrypting encrypted tablespaces
981 #[arg(long)]
982 keyring: Option<String>,
983 },
984
985 /// Cross-validate tablespace files against live MySQL metadata
986 ///
987 /// Scans a data directory for .ibd files and compares their space IDs
988 /// against MySQL's INFORMATION_SCHEMA.INNODB_TABLESPACES. Detects
989 /// orphan files (on disk but not in MySQL), missing tablespaces (in
990 /// MySQL but not on disk), and space ID mismatches. Requires --host
991 /// and --user for MySQL connection (mysql feature must be compiled).
992 Validate {
993 /// Path to MySQL data directory
994 #[arg(short, long)]
995 datadir: String,
996
997 /// Database name to filter (optional)
998 #[arg(short = 'D', long)]
999 database: Option<String>,
1000
1001 /// Deep-validate a specific table (format: db.table or db/table)
1002 #[arg(short = 't', long)]
1003 table: Option<String>,
1004
1005 /// MySQL host
1006 #[arg(long)]
1007 host: Option<String>,
1008
1009 /// MySQL port
1010 #[arg(long)]
1011 port: Option<u16>,
1012
1013 /// MySQL user
1014 #[arg(short, long)]
1015 user: Option<String>,
1016
1017 /// MySQL password
1018 #[arg(short, long)]
1019 password: Option<String>,
1020
1021 /// Path to MySQL defaults file (.my.cnf)
1022 #[arg(long = "defaults-file")]
1023 defaults_file: Option<String>,
1024
1025 /// Show detailed output
1026 #[arg(short, long)]
1027 verbose: bool,
1028
1029 /// Output in JSON format
1030 #[arg(long)]
1031 json: bool,
1032
1033 /// Override page size (default: auto-detect)
1034 #[arg(long = "page-size")]
1035 page_size: Option<u32>,
1036
1037 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
1038 #[arg(long)]
1039 depth: Option<u32>,
1040 },
1041
1042 /// Verify structural integrity of a tablespace
1043 ///
1044 /// Runs pure structural checks on a tablespace file without requiring
1045 /// valid checksums. Checks page number sequence, space ID consistency,
1046 /// LSN monotonicity, B+Tree level validity, page chain bounds, and
1047 /// trailer LSN matching. Exits with code 1 if any check fails.
1048 Verify {
1049 /// Path to InnoDB data file (.ibd)
1050 #[arg(short, long)]
1051 file: String,
1052
1053 /// Show per-page findings
1054 #[arg(short, long)]
1055 verbose: bool,
1056
1057 /// Output in JSON format
1058 #[arg(long)]
1059 json: bool,
1060
1061 /// Override page size (default: auto-detect)
1062 #[arg(long = "page-size")]
1063 page_size: Option<u32>,
1064
1065 /// Path to MySQL keyring file for decrypting encrypted tablespaces
1066 #[arg(long)]
1067 keyring: Option<String>,
1068
1069 /// Path to redo log file to verify LSN continuity against the tablespace
1070 #[arg(long)]
1071 redo: Option<String>,
1072
1073 /// Verify backup chain continuity across multiple tablespace files
1074 #[arg(long = "chain", num_args = 1..)]
1075 chain: Vec<String>,
1076 },
1077
1078 /// Generate shell completion scripts
1079 Completions {
1080 /// Shell to generate completions for
1081 shell: clap_complete::Shell,
1082 },
1083}