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 /// Traverse and display LOB/BLOB chain details for LOB first pages
138 #[arg(long = "lob-chain")]
139 lob_chain: bool,
140
141 /// Output in JSON format
142 #[arg(long)]
143 json: bool,
144
145 /// Override page size (default: auto-detect)
146 #[arg(long = "page-size")]
147 page_size: Option<u32>,
148
149 /// Path to MySQL keyring file for decrypting encrypted tablespaces
150 #[arg(long)]
151 keyring: Option<String>,
152 },
153
154 /// Hex dump of raw page bytes
155 ///
156 /// Operates in two modes: **page mode** (default) reads a full page by
157 /// number and produces a formatted hex dump with file-relative offsets;
158 /// **offset mode** (`--offset`) reads bytes at an arbitrary file position,
159 /// useful for inspecting structures that cross page boundaries. Use
160 /// `--length` to limit the number of bytes shown, or `--raw` to emit
161 /// unformatted binary bytes suitable for piping to other tools.
162 Dump {
163 /// Path to InnoDB data file
164 #[arg(short, long)]
165 file: String,
166
167 /// Page number to dump (default: 0)
168 #[arg(short, long)]
169 page: Option<u64>,
170
171 /// Absolute byte offset to start dumping (bypasses page mode)
172 #[arg(long)]
173 offset: Option<u64>,
174
175 /// Number of bytes to dump (default: page size or 256 for offset mode)
176 #[arg(short, long)]
177 length: Option<usize>,
178
179 /// Output raw binary bytes (no formatting)
180 #[arg(long)]
181 raw: bool,
182
183 /// Override page size (default: auto-detect)
184 #[arg(long = "page-size")]
185 page_size: Option<u32>,
186
187 /// Path to MySQL keyring file for decrypting encrypted tablespaces
188 #[arg(long)]
189 keyring: Option<String>,
190
191 /// Decrypt page before dumping (requires --keyring)
192 #[arg(long)]
193 decrypt: bool,
194 },
195
196 /// Intentionally corrupt pages for testing
197 ///
198 /// Writes random bytes into a tablespace file to simulate data corruption.
199 /// Targets can be the FIL header (`-k`), the record data area (`-r`), or
200 /// an absolute byte offset (`--offset`). If no page is specified, one is
201 /// chosen at random. Use `--verify` to print before/after checksum
202 /// comparisons confirming the page is now invalid — useful for verifying
203 /// that `inno checksum` correctly detects the damage.
204 Corrupt {
205 /// Path to data file
206 #[arg(short, long)]
207 file: String,
208
209 /// Page number to corrupt (random if not specified)
210 #[arg(short, long)]
211 page: Option<u64>,
212
213 /// Number of bytes to corrupt
214 #[arg(short, long, default_value = "1")]
215 bytes: usize,
216
217 /// Corrupt the FIL header area
218 #[arg(short = 'k', long = "header")]
219 header: bool,
220
221 /// Corrupt the record data area
222 #[arg(short, long)]
223 records: bool,
224
225 /// Absolute byte offset to corrupt (bypasses page calculation)
226 #[arg(long)]
227 offset: Option<u64>,
228
229 /// Show before/after checksum comparison
230 #[arg(long)]
231 verify: bool,
232
233 /// Output in JSON format
234 #[arg(long)]
235 json: bool,
236
237 /// Override page size (default: auto-detect)
238 #[arg(long = "page-size")]
239 page_size: Option<u32>,
240 },
241
242 /// Export record-level data from a tablespace
243 ///
244 /// Extracts user records from clustered index leaf pages and outputs
245 /// them as CSV, JSON, or raw hex. Uses SDI metadata (MySQL 8.0+) to
246 /// decode field types and column names. Without SDI, falls back to
247 /// hex-only output.
248 ///
249 /// Supported types: integers (TINYINT–BIGINT), FLOAT, DOUBLE,
250 /// DATE, DATETIME, TIMESTAMP, YEAR, VARCHAR, CHAR. Unsupported
251 /// types (DECIMAL, BLOB, JSON, etc.) are exported as hex strings.
252 ///
253 /// Use `--where-delete-mark` to include only delete-marked records
254 /// (useful for forensic recovery). Use `--system-columns` to include
255 /// DB_TRX_ID and DB_ROLL_PTR in the output.
256 Export {
257 /// Path to InnoDB data file (.ibd)
258 #[arg(short, long)]
259 file: String,
260
261 /// Export records from a specific page only
262 #[arg(short, long)]
263 page: Option<u64>,
264
265 /// Output format: csv, json, or hex
266 #[arg(long, default_value = "csv")]
267 format: String,
268
269 /// Include only delete-marked records
270 #[arg(long = "where-delete-mark")]
271 where_delete_mark: bool,
272
273 /// Include system columns (DB_TRX_ID, DB_ROLL_PTR) in output
274 #[arg(long = "system-columns")]
275 system_columns: bool,
276
277 /// Show additional details
278 #[arg(short, long)]
279 verbose: bool,
280
281 /// Override page size (default: auto-detect)
282 #[arg(long = "page-size")]
283 page_size: Option<u32>,
284
285 /// Path to MySQL keyring file for decrypting encrypted tablespaces
286 #[arg(long)]
287 keyring: Option<String>,
288 },
289
290 /// Search for pages across data directory
291 ///
292 /// Recursively discovers all `.ibd` files under a MySQL data directory,
293 /// opens each as a tablespace, and reads the FIL header of every page
294 /// looking for a matching `page_number` field. Optional `--checksum` and
295 /// `--space-id` filters narrow results when the same page number appears
296 /// in multiple tablespaces. Use `--first` to stop after the first match
297 /// for faster lookups.
298 ///
299 /// With `--corrupt`, scans all pages for checksum mismatches instead of
300 /// searching by page number. Reports corrupt pages with their stored and
301 /// calculated checksums plus corruption pattern classification.
302 Find {
303 /// MySQL data directory path
304 #[arg(short, long)]
305 datadir: String,
306
307 /// Page number to search for
308 #[arg(short, long)]
309 page: Option<u64>,
310
311 /// Checksum to match (page-number search only)
312 #[arg(short, long)]
313 checksum: Option<u32>,
314
315 /// Space ID to match
316 #[arg(short, long)]
317 space_id: Option<u32>,
318
319 /// Scan for pages with checksum mismatches
320 #[arg(long)]
321 corrupt: bool,
322
323 /// Stop at first match
324 #[arg(long)]
325 first: bool,
326
327 /// Output in JSON format
328 #[arg(long)]
329 json: bool,
330
331 /// Override page size (default: auto-detect)
332 #[arg(long = "page-size")]
333 page_size: Option<u32>,
334
335 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
336 #[arg(long)]
337 depth: Option<u32>,
338 },
339
340 /// List/find tablespace IDs
341 ///
342 /// Scans `.ibd` and `.ibu` files under a MySQL data directory and reads
343 /// the space ID from the FSP header (page 0, offset 38) of each file.
344 /// In **list mode** (`-l`) it prints every file and its space ID; in
345 /// **lookup mode** (`-t <id>`) it finds the file that owns a specific
346 /// tablespace ID. Useful for mapping a space ID seen in error logs or
347 /// `INFORMATION_SCHEMA` back to a physical file on disk.
348 Tsid {
349 /// MySQL data directory path
350 #[arg(short, long)]
351 datadir: String,
352
353 /// List all tablespace IDs
354 #[arg(short, long)]
355 list: bool,
356
357 /// Find table file by tablespace ID
358 #[arg(short = 't', long = "tsid")]
359 tablespace_id: Option<u32>,
360
361 /// Output in JSON format
362 #[arg(long)]
363 json: bool,
364
365 /// Override page size (default: auto-detect)
366 #[arg(long = "page-size")]
367 page_size: Option<u32>,
368
369 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
370 #[arg(long)]
371 depth: Option<u32>,
372 },
373
374 /// Extract SDI metadata (MySQL 8.0+)
375 ///
376 /// Locates SDI (Serialized Dictionary Information) pages in a tablespace
377 /// by scanning for page type 17853, then reassembles multi-page SDI
378 /// records by following the page chain. The zlib-compressed payload is
379 /// decompressed and printed as JSON. Each tablespace in MySQL 8.0+
380 /// embeds its own table/column/index definitions as SDI records,
381 /// eliminating the need for the `.frm` files used in older versions.
382 /// Use `--pretty` for indented JSON output.
383 Sdi {
384 /// Path to InnoDB data file (.ibd)
385 #[arg(short, long)]
386 file: String,
387
388 /// Pretty-print JSON output
389 #[arg(short, long)]
390 pretty: bool,
391
392 /// Override page size (default: auto-detect)
393 #[arg(long = "page-size")]
394 page_size: Option<u32>,
395
396 /// Path to MySQL keyring file for decrypting encrypted tablespaces
397 #[arg(long)]
398 keyring: Option<String>,
399 },
400
401 /// Extract schema and reconstruct DDL from tablespace metadata
402 ///
403 /// Reads SDI (Serialized Dictionary Information) from MySQL 8.0+
404 /// tablespaces, parses the embedded data dictionary JSON into typed
405 /// column, index, and foreign key definitions, and reconstructs a
406 /// complete `CREATE TABLE` DDL statement. For pre-8.0 tablespaces
407 /// that lack SDI, scans INDEX pages to infer basic index structure
408 /// and record format (compact vs. redundant).
409 ///
410 /// Use `--verbose` for a structured breakdown of columns, indexes,
411 /// and foreign keys above the DDL. Use `--json` for machine-readable
412 /// output including the full schema definition and DDL as a JSON object.
413 Schema {
414 /// Path to InnoDB data file (.ibd)
415 #[arg(short, long)]
416 file: String,
417
418 /// Show structured schema breakdown above the DDL
419 #[arg(short, long)]
420 verbose: bool,
421
422 /// Output in JSON format
423 #[arg(long)]
424 json: bool,
425
426 /// Override page size (default: auto-detect)
427 #[arg(long = "page-size")]
428 page_size: Option<u32>,
429
430 /// Path to MySQL keyring file for decrypting encrypted tablespaces
431 #[arg(long)]
432 keyring: Option<String>,
433 },
434
435 /// Analyze InnoDB redo log files
436 ///
437 /// Opens an InnoDB redo log file (`ib_logfile0`/`ib_logfile1` for
438 /// MySQL < 8.0.30, or `#ib_redo*` files for 8.0.30+) and displays
439 /// the log file header, both checkpoint records, and per-block details
440 /// including block number, data length, checkpoint number, and CRC-32C
441 /// checksum status. With `--verbose`, MLOG record types within each
442 /// data block are decoded and summarized. Use `--blocks N` to limit
443 /// output to the first N data blocks, or `--no-empty` to skip blocks
444 /// that contain no redo data.
445 Log {
446 /// Path to redo log file (ib_logfile0, ib_logfile1, or #ib_redo*)
447 #[arg(short, long)]
448 file: String,
449
450 /// Limit to first N data blocks
451 #[arg(short, long)]
452 blocks: Option<u64>,
453
454 /// Skip empty blocks
455 #[arg(long)]
456 no_empty: bool,
457
458 /// Display additional information
459 #[arg(short, long)]
460 verbose: bool,
461
462 /// Output in JSON format
463 #[arg(long)]
464 json: bool,
465 },
466
467 /// Show InnoDB file and system information
468 ///
469 /// Operates in three modes. **`--ibdata`** reads the `ibdata1` page 0
470 /// FIL header and redo log checkpoint LSNs. **`--lsn-check`** compares
471 /// the `ibdata1` header LSN with the latest redo log checkpoint LSN to
472 /// detect whether the system tablespace and redo log are in sync (useful
473 /// for diagnosing crash-recovery state). **`-D`/`-t`** queries a live
474 /// MySQL instance via `INFORMATION_SCHEMA.INNODB_TABLES` and
475 /// `INNODB_INDEXES` for tablespace IDs, table IDs, index root pages,
476 /// and key InnoDB status metrics (requires the `mysql` feature).
477 Info {
478 /// Inspect ibdata1 page 0 header
479 #[arg(long)]
480 ibdata: bool,
481
482 /// Compare ibdata1 and redo log LSNs
483 #[arg(long = "lsn-check")]
484 lsn_check: bool,
485
486 /// MySQL data directory path
487 #[arg(short, long)]
488 datadir: Option<String>,
489
490 /// Database name (for table/index info)
491 #[arg(short = 'D', long)]
492 database: Option<String>,
493
494 /// Table name (for table/index info)
495 #[arg(short, long)]
496 table: Option<String>,
497
498 /// MySQL host
499 #[arg(long)]
500 host: Option<String>,
501
502 /// MySQL port
503 #[arg(long)]
504 port: Option<u16>,
505
506 /// MySQL user
507 #[arg(long)]
508 user: Option<String>,
509
510 /// MySQL password
511 #[arg(long)]
512 password: Option<String>,
513
514 /// Path to MySQL defaults file (.my.cnf)
515 #[arg(long = "defaults-file")]
516 defaults_file: Option<String>,
517
518 /// Scan data directory and produce a tablespace ID mapping
519 #[arg(long = "tablespace-map")]
520 tablespace_map: bool,
521
522 /// Output in JSON format
523 #[arg(long)]
524 json: bool,
525
526 /// Override page size (default: auto-detect)
527 #[arg(long = "page-size")]
528 page_size: Option<u32>,
529 },
530
531 /// Recover data from corrupt/damaged tablespace files
532 ///
533 /// Scans a tablespace file and classifies each page as intact, corrupt,
534 /// empty, or unreadable. For INDEX pages, counts recoverable user records
535 /// by walking the compact record chain. Produces a recovery assessment
536 /// showing how many pages and records can be salvaged.
537 ///
538 /// Use `--force` to also extract records from pages with bad checksums
539 /// but valid-looking headers — useful when data is partially damaged
540 /// but the record chain is still intact. Use `--page-size` to override
541 /// page size detection when page 0 is corrupt.
542 ///
543 /// With `--verbose`, per-page details are shown including page type,
544 /// status, LSN, and record count. With `--json`, a structured report
545 /// is emitted including optional per-record detail when combined with
546 /// `--verbose`.
547 Recover {
548 /// Path to InnoDB data file (.ibd)
549 #[arg(short, long)]
550 file: String,
551
552 /// Analyze a single page instead of full scan
553 #[arg(short, long)]
554 page: Option<u64>,
555
556 /// Show per-page details
557 #[arg(short, long)]
558 verbose: bool,
559
560 /// Output in JSON format
561 #[arg(long)]
562 json: bool,
563
564 /// Extract records from corrupt pages with valid headers
565 #[arg(long)]
566 force: bool,
567
568 /// Override page size (critical when page 0 is corrupt)
569 #[arg(long = "page-size")]
570 page_size: Option<u32>,
571
572 /// Path to MySQL keyring file for decrypting encrypted tablespaces
573 #[arg(long)]
574 keyring: Option<String>,
575
576 /// Stream results incrementally for lower memory usage (disables parallel processing)
577 #[arg(long)]
578 streaming: bool,
579
580 /// Write a new tablespace from recoverable pages to the given path
581 #[arg(long)]
582 rebuild: Option<String>,
583 },
584
585 /// Validate page checksums
586 ///
587 /// Reads every page in a tablespace and validates its stored checksum
588 /// against both CRC-32C (MySQL 5.7.7+) and legacy InnoDB algorithms.
589 /// Also checks that the header LSN low-32 bits match the FIL trailer.
590 /// All-zero pages are counted as empty and skipped. With `--verbose`,
591 /// per-page results are printed including the detected algorithm and
592 /// stored vs. calculated values. Exits with code 1 if any page has an
593 /// invalid checksum, making it suitable for use in scripts and CI.
594 Checksum {
595 /// Path to InnoDB data file (.ibd)
596 #[arg(short, long)]
597 file: String,
598
599 /// Show per-page checksum details
600 #[arg(short, long)]
601 verbose: bool,
602
603 /// Output in JSON format
604 #[arg(long)]
605 json: bool,
606
607 /// Override page size (default: auto-detect)
608 #[arg(long = "page-size")]
609 page_size: Option<u32>,
610
611 /// Path to MySQL keyring file for decrypting encrypted tablespaces
612 #[arg(long)]
613 keyring: Option<String>,
614
615 /// Stream results incrementally for lower memory usage (disables parallel processing)
616 #[arg(long)]
617 streaming: bool,
618 },
619
620 /// Monitor a tablespace file for page-level changes
621 ///
622 /// Polls an InnoDB tablespace file at a configurable interval and reports
623 /// which pages have been modified, added, or removed since the last poll.
624 /// Change detection is based on LSN comparison — if a page's LSN changes
625 /// between polls, it was modified by a write. Checksums are validated for
626 /// each changed page to detect corruption during writes.
627 ///
628 /// The tablespace is re-opened each cycle to detect file growth and avoid
629 /// stale file handles. Use `--verbose` for per-field diffs on changed
630 /// pages, or `--json` for NDJSON streaming output (one JSON object per
631 /// line). Press Ctrl+C for a clean exit with a summary of total changes.
632 Watch {
633 /// Path to InnoDB data file (.ibd)
634 #[arg(short, long)]
635 file: String,
636
637 /// Polling interval in milliseconds
638 #[arg(short, long, default_value = "1000")]
639 interval: u64,
640
641 /// Show per-field diffs for changed pages
642 #[arg(short, long)]
643 verbose: bool,
644
645 /// Output in NDJSON streaming format
646 #[arg(long)]
647 json: bool,
648
649 /// Emit per-page NDJSON change events (audit-log compatible)
650 #[arg(long)]
651 events: bool,
652
653 /// Override page size (default: auto-detect)
654 #[arg(long = "page-size")]
655 page_size: Option<u32>,
656
657 /// Path to MySQL keyring file for decrypting encrypted tablespaces
658 #[arg(long)]
659 keyring: Option<String>,
660 },
661
662 /// Repair corrupt page checksums
663 ///
664 /// Recalculates and writes correct checksums for pages with invalid checksums
665 /// or LSN mismatches. By default, auto-detects the checksum algorithm from
666 /// page 0 and creates a `.bak` backup before modifying the file. Use
667 /// `--algorithm` to force a specific algorithm, `--dry-run` to preview
668 /// repairs without modifying the file, or `--no-backup` to skip the backup.
669 Repair {
670 /// Path to InnoDB data file (.ibd)
671 #[arg(short, long)]
672 file: Option<String>,
673
674 /// Repair all .ibd files under a data directory
675 #[arg(long)]
676 batch: Option<String>,
677
678 /// Repair only a specific page number
679 #[arg(short, long)]
680 page: Option<u64>,
681
682 /// Checksum algorithm: auto, crc32c, innodb, full_crc32
683 #[arg(short, long, default_value = "auto")]
684 algorithm: String,
685
686 /// Skip creating a backup before repair
687 #[arg(long)]
688 no_backup: bool,
689
690 /// Preview repairs without modifying the file
691 #[arg(long)]
692 dry_run: bool,
693
694 /// Show per-page repair details
695 #[arg(short, long)]
696 verbose: bool,
697
698 /// Output in JSON format
699 #[arg(long)]
700 json: bool,
701
702 /// Override page size (default: auto-detect)
703 #[arg(long = "page-size")]
704 page_size: Option<u32>,
705
706 /// Path to MySQL keyring file for decrypting encrypted tablespaces
707 #[arg(long)]
708 keyring: Option<String>,
709 },
710
711 /// Compare two tablespace files page-by-page
712 ///
713 /// Reads two InnoDB tablespace files and compares them page-by-page,
714 /// reporting which pages are identical, modified, or only present in
715 /// one file. With `--verbose`, per-page FIL header field diffs are
716 /// shown for modified pages, highlighting changes to checksums, LSNs,
717 /// page types, and space IDs. Add `--byte-ranges` (with `-v`) to see
718 /// the exact byte offsets where page content differs. Use `-p` to
719 /// compare a single page, or `--json` for machine-readable output.
720 ///
721 /// When files have different page sizes, only FIL headers (first 38
722 /// bytes) are compared and a warning is displayed.
723 Diff {
724 /// First InnoDB data file (.ibd)
725 file1: String,
726
727 /// Second InnoDB data file (.ibd)
728 file2: String,
729
730 /// Show per-page header field diffs
731 #[arg(short, long)]
732 verbose: bool,
733
734 /// Show byte-range diffs for changed pages (requires -v)
735 #[arg(short = 'b', long = "byte-ranges")]
736 byte_ranges: bool,
737
738 /// Compare a single page only
739 #[arg(short, long)]
740 page: Option<u64>,
741
742 /// Annotate diff with MySQL version information from SDI metadata
743 #[arg(long = "version-aware")]
744 version_aware: bool,
745
746 /// Output in JSON format
747 #[arg(long)]
748 json: bool,
749
750 /// Override page size (default: auto-detect)
751 #[arg(long = "page-size")]
752 page_size: Option<u32>,
753
754 /// Path to MySQL keyring file for decrypting encrypted tablespaces
755 #[arg(long)]
756 keyring: Option<String>,
757 },
758
759 /// Copy specific pages from a donor tablespace into a target
760 ///
761 /// Reads pages from the donor file and writes them into the target file at
762 /// the same page number. Safety checks ensure page sizes and space IDs
763 /// match. Page 0 (FSP_HDR) is rejected unless `--force` is used. Donor
764 /// pages with invalid checksums are skipped unless `--force` is used.
765 ///
766 /// A backup of the target is created by default. Use `--dry-run` to preview
767 /// which pages would be transplanted without modifying the target.
768 Transplant {
769 /// Path to donor tablespace file (source of pages)
770 donor: String,
771
772 /// Path to target tablespace file (destination)
773 target: String,
774
775 /// Page numbers to transplant (comma-separated)
776 #[arg(short, long, value_delimiter = ',')]
777 pages: Vec<u64>,
778
779 /// Skip creating a backup of the target
780 #[arg(long)]
781 no_backup: bool,
782
783 /// Allow space ID mismatch, corrupt donor pages, and page 0 transplant
784 #[arg(long)]
785 force: bool,
786
787 /// Preview without modifying the target file
788 #[arg(long)]
789 dry_run: bool,
790
791 /// Show per-page details
792 #[arg(short, long)]
793 verbose: bool,
794
795 /// Output in JSON format
796 #[arg(long)]
797 json: bool,
798
799 /// Override page size (default: auto-detect)
800 #[arg(long = "page-size")]
801 page_size: Option<u32>,
802
803 /// Path to MySQL keyring file for decrypting encrypted tablespaces
804 #[arg(long)]
805 keyring: Option<String>,
806 },
807
808 /// Per-index B+Tree health metrics
809 ///
810 /// Scans all INDEX pages in a tablespace and computes per-index health
811 /// metrics including fill factor (average, min, max), garbage ratio,
812 /// fragmentation, tree depth, and page counts. Optionally resolves
813 /// index names from SDI metadata (MySQL 8.0+).
814 ///
815 /// Use `--verbose` for additional detail including total records and
816 /// empty leaf page counts. Use `--json` for machine-readable output.
817 Health {
818 /// Path to InnoDB data file (.ibd)
819 #[arg(short, long)]
820 file: String,
821
822 /// Show additional detail (records, empty leaves)
823 #[arg(short, long)]
824 verbose: bool,
825
826 /// Output in JSON format
827 #[arg(long, conflicts_with = "prometheus")]
828 json: bool,
829
830 /// Output metrics in Prometheus exposition format
831 #[arg(long, conflicts_with = "json")]
832 prometheus: bool,
833
834 /// Compute index bloat scores (A-F grades)
835 #[arg(long)]
836 bloat: bool,
837
838 /// Estimate cardinality of leading index columns via page sampling
839 #[arg(long)]
840 cardinality: bool,
841
842 /// Number of leaf pages to sample per index for cardinality estimation
843 #[arg(long = "sample-size", default_value = "30")]
844 sample_size: usize,
845
846 /// Override page size (default: auto-detect)
847 #[arg(long = "page-size")]
848 page_size: Option<u32>,
849
850 /// Path to MySQL keyring file for decrypting encrypted tablespaces
851 #[arg(long)]
852 keyring: Option<String>,
853 },
854
855 /// Audit a MySQL data directory for integrity, health, or corruption
856 ///
857 /// Scans all `.ibd` files under a data directory and validates checksums,
858 /// computes health metrics, or lists corrupt pages — replacing the need
859 /// to run `inno checksum` or `inno health` file-by-file. Three modes:
860 ///
861 /// **Default (integrity)**: validates checksums across all tablespace files
862 /// and reports per-file pass/fail with a directory-wide integrity percentage.
863 ///
864 /// **`--health`**: computes per-tablespace fill factor, fragmentation, and
865 /// garbage ratio, ranked worst-first. Use `--min-fill-factor` and
866 /// `--max-fragmentation` to filter for unhealthy tablespaces only.
867 ///
868 /// **`--checksum-mismatch`**: compact listing of only corrupt pages with
869 /// stored vs. calculated checksums, suitable for piping to `inno repair`.
870 ///
871 /// `--health` and `--checksum-mismatch` are mutually exclusive.
872 Audit {
873 /// MySQL data directory path
874 #[arg(short, long)]
875 datadir: String,
876
877 /// Show per-tablespace health metrics instead of checksum validation
878 #[arg(long)]
879 health: bool,
880
881 /// List only pages with checksum mismatches (compact output)
882 #[arg(long = "checksum-mismatch", conflicts_with = "prometheus")]
883 checksum_mismatch: bool,
884
885 /// Show additional details (per-page results in default mode)
886 #[arg(short, long)]
887 verbose: bool,
888
889 /// Output in JSON format
890 #[arg(long, conflicts_with = "prometheus")]
891 json: bool,
892
893 /// Output metrics in Prometheus exposition format
894 #[arg(long, conflicts_with = "json", conflicts_with = "checksum_mismatch")]
895 prometheus: bool,
896
897 /// Override page size (default: auto-detect per file)
898 #[arg(long = "page-size")]
899 page_size: Option<u32>,
900
901 /// Path to MySQL keyring file for decrypting encrypted tablespaces
902 #[arg(long)]
903 keyring: Option<String>,
904
905 /// Show tables with fill factor below this threshold (0-100, --health only)
906 #[arg(long = "min-fill-factor")]
907 min_fill_factor: Option<f64>,
908
909 /// Show tables with fragmentation above this threshold (0-100, --health only)
910 #[arg(long = "max-fragmentation")]
911 max_fragmentation: Option<f64>,
912
913 /// Enable bloat scoring for --health mode
914 #[arg(long)]
915 bloat: bool,
916
917 /// Filter: show tables with worst bloat grade at or worse than threshold (A-F, --health only)
918 #[arg(long = "max-bloat-grade")]
919 max_bloat_grade: Option<String>,
920
921 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
922 #[arg(long)]
923 depth: Option<u32>,
924 },
925
926 /// Check tablespace compatibility with a target MySQL version
927 ///
928 /// Analyzes tablespace files to determine whether they are compatible
929 /// with a target MySQL version. Checks include page size support, row
930 /// format, encryption, compression, SDI presence, and vendor
931 /// compatibility (MariaDB tablespaces are flagged as incompatible).
932 ///
933 /// **Single-file mode** (`--file`): checks one tablespace and reports
934 /// all compatibility findings with severity levels (info/warning/error).
935 ///
936 /// **Directory scan mode** (`--scan`): discovers all `.ibd` files under
937 /// a data directory, runs compatibility checks on each in parallel, and
938 /// produces a per-file summary plus an overall compatible/incompatible
939 /// count.
940 ///
941 /// `--file` and `--scan` are mutually exclusive.
942 Compat {
943 /// Path to InnoDB data file (.ibd)
944 #[arg(short, long)]
945 file: Option<String>,
946
947 /// Scan a data directory for compatibility issues (mutually exclusive with --file)
948 #[arg(short, long)]
949 scan: Option<String>,
950
951 /// Target MySQL version (e.g., "8.4.0", "9.0.0")
952 #[arg(short, long)]
953 target: String,
954
955 /// Show detailed check information
956 #[arg(short, long)]
957 verbose: bool,
958
959 /// Output in JSON format
960 #[arg(long)]
961 json: bool,
962
963 /// Override page size (default: auto-detect)
964 #[arg(long = "page-size")]
965 page_size: Option<u32>,
966
967 /// Path to MySQL keyring file for decrypting encrypted tablespaces
968 #[arg(long)]
969 keyring: Option<String>,
970
971 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
972 #[arg(long)]
973 depth: Option<u32>,
974 },
975
976 /// Defragment a tablespace by reclaiming free space and reordering pages
977 ///
978 /// Reads all pages from a source tablespace, removes empty and corrupt
979 /// pages, sorts INDEX pages by (index_id, level, page_number), fixes
980 /// prev/next chain pointers within each index group, renumbers pages
981 /// sequentially, rebuilds page 0, recalculates all checksums, and writes
982 /// the result to a new output file. The source file is never modified.
983 Defrag {
984 /// Path to source InnoDB data file (.ibd)
985 #[arg(short, long)]
986 file: String,
987
988 /// Path to output file (required — always writes a new file)
989 #[arg(short, long)]
990 output: String,
991
992 /// Show per-page details
993 #[arg(short, long)]
994 verbose: bool,
995
996 /// Output in JSON format
997 #[arg(long)]
998 json: bool,
999
1000 /// Override page size (default: auto-detect)
1001 #[arg(long = "page-size")]
1002 page_size: Option<u32>,
1003
1004 /// Path to MySQL keyring file for decrypting encrypted tablespaces
1005 #[arg(long)]
1006 keyring: Option<String>,
1007 },
1008
1009 /// Cross-validate tablespace files against live MySQL metadata
1010 ///
1011 /// Scans a data directory for .ibd files and compares their space IDs
1012 /// against MySQL's INFORMATION_SCHEMA.INNODB_TABLESPACES. Detects
1013 /// orphan files (on disk but not in MySQL), missing tablespaces (in
1014 /// MySQL but not on disk), and space ID mismatches. Requires --host
1015 /// and --user for MySQL connection (mysql feature must be compiled).
1016 Validate {
1017 /// Path to MySQL data directory
1018 #[arg(short, long)]
1019 datadir: String,
1020
1021 /// Database name to filter (optional)
1022 #[arg(short = 'D', long)]
1023 database: Option<String>,
1024
1025 /// Deep-validate a specific table (format: db.table or db/table)
1026 #[arg(short = 't', long)]
1027 table: Option<String>,
1028
1029 /// MySQL host
1030 #[arg(long)]
1031 host: Option<String>,
1032
1033 /// MySQL port
1034 #[arg(long)]
1035 port: Option<u16>,
1036
1037 /// MySQL user
1038 #[arg(short, long)]
1039 user: Option<String>,
1040
1041 /// MySQL password
1042 #[arg(short, long)]
1043 password: Option<String>,
1044
1045 /// Path to MySQL defaults file (.my.cnf)
1046 #[arg(long = "defaults-file")]
1047 defaults_file: Option<String>,
1048
1049 /// Show detailed output
1050 #[arg(short, long)]
1051 verbose: bool,
1052
1053 /// Output in JSON format
1054 #[arg(long)]
1055 json: bool,
1056
1057 /// Override page size (default: auto-detect)
1058 #[arg(long = "page-size")]
1059 page_size: Option<u32>,
1060
1061 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
1062 #[arg(long)]
1063 depth: Option<u32>,
1064 },
1065
1066 /// Verify structural integrity of a tablespace
1067 ///
1068 /// Runs pure structural checks on a tablespace file without requiring
1069 /// valid checksums. Checks page number sequence, space ID consistency,
1070 /// LSN monotonicity, B+Tree level validity, page chain bounds, and
1071 /// trailer LSN matching. Exits with code 1 if any check fails.
1072 Verify {
1073 /// Path to InnoDB data file (.ibd)
1074 #[arg(short, long)]
1075 file: String,
1076
1077 /// Show per-page findings
1078 #[arg(short, long)]
1079 verbose: bool,
1080
1081 /// Output in JSON format
1082 #[arg(long)]
1083 json: bool,
1084
1085 /// Override page size (default: auto-detect)
1086 #[arg(long = "page-size")]
1087 page_size: Option<u32>,
1088
1089 /// Path to MySQL keyring file for decrypting encrypted tablespaces
1090 #[arg(long)]
1091 keyring: Option<String>,
1092
1093 /// Path to redo log file to verify LSN continuity against the tablespace
1094 #[arg(long)]
1095 redo: Option<String>,
1096
1097 /// Verify backup chain continuity across multiple tablespace files
1098 #[arg(long = "chain", num_args = 1..)]
1099 chain: Vec<String>,
1100
1101 /// Path to XtraBackup checkpoint file to cross-reference LSNs
1102 #[arg(long = "backup-meta")]
1103 backup_meta: Option<String>,
1104 },
1105
1106 /// Parse and analyze MySQL binary log files
1107 ///
1108 /// Reads the format description event, then iterates all events in the
1109 /// binary log to produce type distribution statistics, table map details,
1110 /// and an event listing. Supports filtering by event type and limiting
1111 /// the number of events displayed.
1112 Binlog {
1113 /// Path to MySQL binary log file
1114 #[arg(short, long)]
1115 file: String,
1116
1117 /// Maximum number of events to display
1118 #[arg(short, long)]
1119 limit: Option<usize>,
1120
1121 /// Filter events by type name (e.g. TABLE_MAP, WRITE_ROWS)
1122 #[arg(long = "filter-type")]
1123 filter_type: Option<String>,
1124
1125 /// Show additional detail (column types for TABLE_MAP events)
1126 #[arg(short, long)]
1127 verbose: bool,
1128
1129 /// Output in JSON format
1130 #[arg(long)]
1131 json: bool,
1132 },
1133
1134 /// Analyze undo tablespace files (.ibu or .ibd)
1135 ///
1136 /// Reads rollback segment arrays, rollback segment headers, and undo
1137 /// segment pages to report transaction history and segment states.
1138 /// Supports MySQL 8.0+ dedicated undo tablespaces (.ibu) and legacy
1139 /// system tablespace undo logs.
1140 Undo {
1141 /// Path to InnoDB undo tablespace file (.ibu or .ibd)
1142 #[arg(short, long)]
1143 file: String,
1144
1145 /// Show a specific undo page only
1146 #[arg(short, long)]
1147 page: Option<u64>,
1148
1149 /// Show additional detail including undo records
1150 #[arg(short, long)]
1151 verbose: bool,
1152
1153 /// Output in JSON format
1154 #[arg(long)]
1155 json: bool,
1156
1157 /// Override page size (default: auto-detect)
1158 #[arg(long = "page-size")]
1159 page_size: Option<u32>,
1160
1161 /// Path to MySQL keyring file for decrypting encrypted tablespaces
1162 #[arg(long)]
1163 keyring: Option<String>,
1164 },
1165
1166 /// Recover deleted records from a tablespace
1167 ///
1168 /// Scans InnoDB tablespace files for deleted records using three strategies:
1169 /// delete-marked records still in the active B+Tree chain (confidence 1.0),
1170 /// records in the page free list (confidence 0.3-0.7), and optionally
1171 /// DEL_MARK_REC entries in undo log pages (confidence 0.1-0.3).
1172 ///
1173 /// Output formats include CSV (default), JSON array of record objects,
1174 /// SQL INSERT statements, or hex dump. Use `--json` for a full metadata
1175 /// envelope including summary statistics.
1176 ///
1177 /// With `--undo-file`, provide an undo tablespace (ibdata1 or .ibu) to
1178 /// enable undo log scanning for additional PK-only recovery.
1179 Undelete {
1180 /// Path to InnoDB data file (.ibd)
1181 #[arg(short, long)]
1182 file: String,
1183
1184 /// Path to undo tablespace (ibdata1 or .ibu) for undo log scanning
1185 #[arg(long = "undo-file")]
1186 undo_file: Option<String>,
1187
1188 /// Filter by table name
1189 #[arg(short, long)]
1190 table: Option<String>,
1191
1192 /// Minimum transaction ID to include
1193 #[arg(long = "min-trx-id")]
1194 min_trx_id: Option<u64>,
1195
1196 /// Minimum confidence threshold (0.0-1.0, default: 0.0)
1197 #[arg(long, default_value = "0.0")]
1198 confidence: f64,
1199
1200 /// Record output format: csv, json, sql, hex
1201 #[arg(long, default_value = "csv")]
1202 format: String,
1203
1204 /// Show additional details
1205 #[arg(short, long)]
1206 verbose: bool,
1207
1208 /// Recover from a specific page only
1209 #[arg(short, long)]
1210 page: Option<u64>,
1211
1212 /// Output full metadata JSON envelope
1213 #[arg(long)]
1214 json: bool,
1215
1216 /// Override page size (default: auto-detect)
1217 #[arg(long = "page-size")]
1218 page_size: Option<u32>,
1219
1220 /// Path to MySQL keyring file for decrypting encrypted tablespaces
1221 #[arg(long)]
1222 keyring: Option<String>,
1223 },
1224
1225 /// Simulate InnoDB crash recovery levels
1226 ///
1227 /// Analyzes a tablespace file (or all tablespaces in a data directory) and
1228 /// predicts data recoverability at each `innodb_force_recovery` level (1-6).
1229 /// Classifies every page by the minimum recovery level needed to access it,
1230 /// then aggregates per-index and per-table impact estimates. Recommends the
1231 /// optimal recovery level that preserves the most data.
1232 ///
1233 /// Use `--level N` to focus on a specific recovery level, `--verbose` for
1234 /// per-page details, or `--datadir` to scan an entire data directory.
1235 Simulate {
1236 /// Path to InnoDB data file (.ibd)
1237 #[arg(short, long, required_unless_present = "datadir")]
1238 file: Option<String>,
1239
1240 /// Path to MySQL data directory (simulates all tablespaces)
1241 #[arg(short, long, required_unless_present = "file")]
1242 datadir: Option<String>,
1243
1244 /// Show detailed analysis at specific recovery level (1-6)
1245 #[arg(short, long, value_parser = clap::value_parser!(u8).range(1..=6))]
1246 level: Option<u8>,
1247
1248 /// Show per-page details
1249 #[arg(short, long)]
1250 verbose: bool,
1251
1252 /// Output in JSON format
1253 #[arg(long)]
1254 json: bool,
1255
1256 /// Override page size (default: auto-detect)
1257 #[arg(long = "page-size")]
1258 page_size: Option<u32>,
1259
1260 /// Path to MySQL keyring file for decrypting encrypted tablespaces
1261 #[arg(long)]
1262 keyring: Option<String>,
1263
1264 /// Maximum directory recursion depth (default: 2, 0 = unlimited)
1265 #[arg(long)]
1266 depth: Option<u32>,
1267 },
1268
1269 /// Analyze incremental backups and detect changed pages
1270 ///
1271 /// Two modes: `diff` compares page LSNs between a base (backup) and
1272 /// current (live) tablespace to detect which pages were modified since
1273 /// the backup; `chain` validates an XtraBackup backup directory for
1274 /// LSN continuity across full and incremental backup sets.
1275 Backup {
1276 #[command(subcommand)]
1277 subcmd: BackupSubcommand,
1278 },
1279
1280 /// Generate shell completion scripts
1281 Completions {
1282 /// Shell to generate completions for
1283 shell: clap_complete::Shell,
1284 },
1285}
1286
1287/// Subcommands for `inno backup`.
1288#[derive(Subcommand)]
1289pub enum BackupSubcommand {
1290 /// Compare page LSNs between base backup and current tablespace
1291 Diff {
1292 /// Path to the base/backup tablespace file (.ibd)
1293 #[arg(long)]
1294 base: String,
1295
1296 /// Path to the current/live tablespace file (.ibd)
1297 #[arg(long)]
1298 current: String,
1299
1300 /// Show per-page delta details
1301 #[arg(short, long)]
1302 verbose: bool,
1303
1304 /// Output in JSON format
1305 #[arg(long)]
1306 json: bool,
1307
1308 /// Override page size (default: auto-detect)
1309 #[arg(long = "page-size")]
1310 page_size: Option<u32>,
1311
1312 /// Path to MySQL keyring file for decrypting encrypted tablespaces
1313 #[arg(long)]
1314 keyring: Option<String>,
1315 },
1316
1317 /// Validate backup chain LSN continuity
1318 Chain {
1319 /// Path to directory containing backup sets with xtrabackup_checkpoints
1320 #[arg(short, long)]
1321 dir: String,
1322
1323 /// Show details for each backup set
1324 #[arg(short, long)]
1325 verbose: bool,
1326
1327 /// Output in JSON format
1328 #[arg(long)]
1329 json: bool,
1330 },
1331}