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        /// 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}