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