Skip to main content

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        /// Override page size (default: auto-detect)
835        #[arg(long = "page-size")]
836        page_size: Option<u32>,
837
838        /// Path to MySQL keyring file for decrypting encrypted tablespaces
839        #[arg(long)]
840        keyring: Option<String>,
841    },
842
843    /// Audit a MySQL data directory for integrity, health, or corruption
844    ///
845    /// Scans all `.ibd` files under a data directory and validates checksums,
846    /// computes health metrics, or lists corrupt pages — replacing the need
847    /// to run `inno checksum` or `inno health` file-by-file. Three modes:
848    ///
849    /// **Default (integrity)**: validates checksums across all tablespace files
850    /// and reports per-file pass/fail with a directory-wide integrity percentage.
851    ///
852    /// **`--health`**: computes per-tablespace fill factor, fragmentation, and
853    /// garbage ratio, ranked worst-first. Use `--min-fill-factor` and
854    /// `--max-fragmentation` to filter for unhealthy tablespaces only.
855    ///
856    /// **`--checksum-mismatch`**: compact listing of only corrupt pages with
857    /// stored vs. calculated checksums, suitable for piping to `inno repair`.
858    ///
859    /// `--health` and `--checksum-mismatch` are mutually exclusive.
860    Audit {
861        /// MySQL data directory path
862        #[arg(short, long)]
863        datadir: String,
864
865        /// Show per-tablespace health metrics instead of checksum validation
866        #[arg(long)]
867        health: bool,
868
869        /// List only pages with checksum mismatches (compact output)
870        #[arg(long = "checksum-mismatch", conflicts_with = "prometheus")]
871        checksum_mismatch: bool,
872
873        /// Show additional details (per-page results in default mode)
874        #[arg(short, long)]
875        verbose: bool,
876
877        /// Output in JSON format
878        #[arg(long, conflicts_with = "prometheus")]
879        json: bool,
880
881        /// Output metrics in Prometheus exposition format
882        #[arg(long, conflicts_with = "json", conflicts_with = "checksum_mismatch")]
883        prometheus: bool,
884
885        /// Override page size (default: auto-detect per file)
886        #[arg(long = "page-size")]
887        page_size: Option<u32>,
888
889        /// Path to MySQL keyring file for decrypting encrypted tablespaces
890        #[arg(long)]
891        keyring: Option<String>,
892
893        /// Show tables with fill factor below this threshold (0-100, --health only)
894        #[arg(long = "min-fill-factor")]
895        min_fill_factor: Option<f64>,
896
897        /// Show tables with fragmentation above this threshold (0-100, --health only)
898        #[arg(long = "max-fragmentation")]
899        max_fragmentation: Option<f64>,
900
901        /// Maximum directory recursion depth (default: 2, 0 = unlimited)
902        #[arg(long)]
903        depth: Option<u32>,
904    },
905
906    /// Check tablespace compatibility with a target MySQL version
907    ///
908    /// Analyzes tablespace files to determine whether they are compatible
909    /// with a target MySQL version. Checks include page size support, row
910    /// format, encryption, compression, SDI presence, and vendor
911    /// compatibility (MariaDB tablespaces are flagged as incompatible).
912    ///
913    /// **Single-file mode** (`--file`): checks one tablespace and reports
914    /// all compatibility findings with severity levels (info/warning/error).
915    ///
916    /// **Directory scan mode** (`--scan`): discovers all `.ibd` files under
917    /// a data directory, runs compatibility checks on each in parallel, and
918    /// produces a per-file summary plus an overall compatible/incompatible
919    /// count.
920    ///
921    /// `--file` and `--scan` are mutually exclusive.
922    Compat {
923        /// Path to InnoDB data file (.ibd)
924        #[arg(short, long)]
925        file: Option<String>,
926
927        /// Scan a data directory for compatibility issues (mutually exclusive with --file)
928        #[arg(short, long)]
929        scan: Option<String>,
930
931        /// Target MySQL version (e.g., "8.4.0", "9.0.0")
932        #[arg(short, long)]
933        target: String,
934
935        /// Show detailed check information
936        #[arg(short, long)]
937        verbose: bool,
938
939        /// Output in JSON format
940        #[arg(long)]
941        json: bool,
942
943        /// Override page size (default: auto-detect)
944        #[arg(long = "page-size")]
945        page_size: Option<u32>,
946
947        /// Path to MySQL keyring file for decrypting encrypted tablespaces
948        #[arg(long)]
949        keyring: Option<String>,
950
951        /// Maximum directory recursion depth (default: 2, 0 = unlimited)
952        #[arg(long)]
953        depth: Option<u32>,
954    },
955
956    /// Defragment a tablespace by reclaiming free space and reordering pages
957    ///
958    /// Reads all pages from a source tablespace, removes empty and corrupt
959    /// pages, sorts INDEX pages by (index_id, level, page_number), fixes
960    /// prev/next chain pointers within each index group, renumbers pages
961    /// sequentially, rebuilds page 0, recalculates all checksums, and writes
962    /// the result to a new output file. The source file is never modified.
963    Defrag {
964        /// Path to source InnoDB data file (.ibd)
965        #[arg(short, long)]
966        file: String,
967
968        /// Path to output file (required — always writes a new file)
969        #[arg(short, long)]
970        output: String,
971
972        /// Show per-page details
973        #[arg(short, long)]
974        verbose: bool,
975
976        /// Output in JSON format
977        #[arg(long)]
978        json: bool,
979
980        /// Override page size (default: auto-detect)
981        #[arg(long = "page-size")]
982        page_size: Option<u32>,
983
984        /// Path to MySQL keyring file for decrypting encrypted tablespaces
985        #[arg(long)]
986        keyring: Option<String>,
987    },
988
989    /// Cross-validate tablespace files against live MySQL metadata
990    ///
991    /// Scans a data directory for .ibd files and compares their space IDs
992    /// against MySQL's INFORMATION_SCHEMA.INNODB_TABLESPACES. Detects
993    /// orphan files (on disk but not in MySQL), missing tablespaces (in
994    /// MySQL but not on disk), and space ID mismatches. Requires --host
995    /// and --user for MySQL connection (mysql feature must be compiled).
996    Validate {
997        /// Path to MySQL data directory
998        #[arg(short, long)]
999        datadir: String,
1000
1001        /// Database name to filter (optional)
1002        #[arg(short = 'D', long)]
1003        database: Option<String>,
1004
1005        /// Deep-validate a specific table (format: db.table or db/table)
1006        #[arg(short = 't', long)]
1007        table: Option<String>,
1008
1009        /// MySQL host
1010        #[arg(long)]
1011        host: Option<String>,
1012
1013        /// MySQL port
1014        #[arg(long)]
1015        port: Option<u16>,
1016
1017        /// MySQL user
1018        #[arg(short, long)]
1019        user: Option<String>,
1020
1021        /// MySQL password
1022        #[arg(short, long)]
1023        password: Option<String>,
1024
1025        /// Path to MySQL defaults file (.my.cnf)
1026        #[arg(long = "defaults-file")]
1027        defaults_file: Option<String>,
1028
1029        /// Show detailed output
1030        #[arg(short, long)]
1031        verbose: bool,
1032
1033        /// Output in JSON format
1034        #[arg(long)]
1035        json: bool,
1036
1037        /// Override page size (default: auto-detect)
1038        #[arg(long = "page-size")]
1039        page_size: Option<u32>,
1040
1041        /// Maximum directory recursion depth (default: 2, 0 = unlimited)
1042        #[arg(long)]
1043        depth: Option<u32>,
1044    },
1045
1046    /// Verify structural integrity of a tablespace
1047    ///
1048    /// Runs pure structural checks on a tablespace file without requiring
1049    /// valid checksums. Checks page number sequence, space ID consistency,
1050    /// LSN monotonicity, B+Tree level validity, page chain bounds, and
1051    /// trailer LSN matching. Exits with code 1 if any check fails.
1052    Verify {
1053        /// Path to InnoDB data file (.ibd)
1054        #[arg(short, long)]
1055        file: String,
1056
1057        /// Show per-page findings
1058        #[arg(short, long)]
1059        verbose: bool,
1060
1061        /// Output in JSON format
1062        #[arg(long)]
1063        json: bool,
1064
1065        /// Override page size (default: auto-detect)
1066        #[arg(long = "page-size")]
1067        page_size: Option<u32>,
1068
1069        /// Path to MySQL keyring file for decrypting encrypted tablespaces
1070        #[arg(long)]
1071        keyring: Option<String>,
1072
1073        /// Path to redo log file to verify LSN continuity against the tablespace
1074        #[arg(long)]
1075        redo: Option<String>,
1076
1077        /// Verify backup chain continuity across multiple tablespace files
1078        #[arg(long = "chain", num_args = 1..)]
1079        chain: Vec<String>,
1080    },
1081
1082    /// Parse and analyze MySQL binary log files
1083    ///
1084    /// Reads the format description event, then iterates all events in the
1085    /// binary log to produce type distribution statistics, table map details,
1086    /// and an event listing. Supports filtering by event type and limiting
1087    /// the number of events displayed.
1088    Binlog {
1089        /// Path to MySQL binary log file
1090        #[arg(short, long)]
1091        file: String,
1092
1093        /// Maximum number of events to display
1094        #[arg(short, long)]
1095        limit: Option<usize>,
1096
1097        /// Filter events by type name (e.g. TABLE_MAP, WRITE_ROWS)
1098        #[arg(long = "filter-type")]
1099        filter_type: Option<String>,
1100
1101        /// Show additional detail (column types for TABLE_MAP events)
1102        #[arg(short, long)]
1103        verbose: bool,
1104
1105        /// Output in JSON format
1106        #[arg(long)]
1107        json: bool,
1108    },
1109
1110    /// Analyze undo tablespace files (.ibu or .ibd)
1111    ///
1112    /// Reads rollback segment arrays, rollback segment headers, and undo
1113    /// segment pages to report transaction history and segment states.
1114    /// Supports MySQL 8.0+ dedicated undo tablespaces (.ibu) and legacy
1115    /// system tablespace undo logs.
1116    Undo {
1117        /// Path to InnoDB undo tablespace file (.ibu or .ibd)
1118        #[arg(short, long)]
1119        file: String,
1120
1121        /// Show a specific undo page only
1122        #[arg(short, long)]
1123        page: Option<u64>,
1124
1125        /// Show additional detail including undo records
1126        #[arg(short, long)]
1127        verbose: bool,
1128
1129        /// Output in JSON format
1130        #[arg(long)]
1131        json: bool,
1132
1133        /// Override page size (default: auto-detect)
1134        #[arg(long = "page-size")]
1135        page_size: Option<u32>,
1136
1137        /// Path to MySQL keyring file for decrypting encrypted tablespaces
1138        #[arg(long)]
1139        keyring: Option<String>,
1140    },
1141
1142    /// Recover deleted records from a tablespace
1143    ///
1144    /// Scans InnoDB tablespace files for deleted records using three strategies:
1145    /// delete-marked records still in the active B+Tree chain (confidence 1.0),
1146    /// records in the page free list (confidence 0.3-0.7), and optionally
1147    /// DEL_MARK_REC entries in undo log pages (confidence 0.1-0.3).
1148    ///
1149    /// Output formats include CSV (default), JSON array of record objects,
1150    /// SQL INSERT statements, or hex dump. Use `--json` for a full metadata
1151    /// envelope including summary statistics.
1152    ///
1153    /// With `--undo-file`, provide an undo tablespace (ibdata1 or .ibu) to
1154    /// enable undo log scanning for additional PK-only recovery.
1155    Undelete {
1156        /// Path to InnoDB data file (.ibd)
1157        #[arg(short, long)]
1158        file: String,
1159
1160        /// Path to undo tablespace (ibdata1 or .ibu) for undo log scanning
1161        #[arg(long = "undo-file")]
1162        undo_file: Option<String>,
1163
1164        /// Filter by table name
1165        #[arg(short, long)]
1166        table: Option<String>,
1167
1168        /// Minimum transaction ID to include
1169        #[arg(long = "min-trx-id")]
1170        min_trx_id: Option<u64>,
1171
1172        /// Minimum confidence threshold (0.0-1.0, default: 0.0)
1173        #[arg(long, default_value = "0.0")]
1174        confidence: f64,
1175
1176        /// Record output format: csv, json, sql, hex
1177        #[arg(long, default_value = "csv")]
1178        format: String,
1179
1180        /// Show additional details
1181        #[arg(short, long)]
1182        verbose: bool,
1183
1184        /// Recover from a specific page only
1185        #[arg(short, long)]
1186        page: Option<u64>,
1187
1188        /// Output full metadata JSON envelope
1189        #[arg(long)]
1190        json: bool,
1191
1192        /// Override page size (default: auto-detect)
1193        #[arg(long = "page-size")]
1194        page_size: Option<u32>,
1195
1196        /// Path to MySQL keyring file for decrypting encrypted tablespaces
1197        #[arg(long)]
1198        keyring: Option<String>,
1199    },
1200
1201    /// Generate shell completion scripts
1202    Completions {
1203        /// Shell to generate completions for
1204        shell: clap_complete::Shell,
1205    },
1206}